package cluedo;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.*;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.Timer;

import msgcomm.Hooks;
import msgcomm.Receiver;
import msgcomm.RmiReceiver;
import msgcomm.messages.AreYouAlive;
import msgcomm.messages.Message;
import msgcomm.messages.NotifyChangeGroup;
import msgcomm.messages.NotifyReceiverCrashed;
import msgcomm.messages.Payload;
import msgcomm.messages.RequestRefreshName;
import msgcomm.transceivers.RmiReliableCausalOrderingMulticast;

import common.CommonConstants;
import common.Logger;

import cluedo.gui.CluedoStart;
import cluedo.gui.MainWindow;
import cluedo.gui.PawnSelection;

import cluedo.messages.*;

/**
 * Questa classe implementa un singolo player.
 * Prima che questo player venga registrato presso il Registration Server, ne viene
 * fatto l'export, in modo da poter essere invocato in modo remoto dagli
 * altri player. La rimozione di questo player dal Registration Server comporta
 * anche il successivo unexport dello stesso.
 * Nota che non viene effettuato il bind dei player. L'unico modo per ottenere
 * il riferimento di un altro player e' attraverso il Registration Server.
 * 
 * La fase di preparazione avviene in questo modo:
 *  1. Il proprietario decide di iniziare la partita. Gli altri player vengono
 *     notificati
 *  2. Il proprietario distribuisce le carte
 *  3. A turno, iniziando dal proprietario, vengono scelte le pedine
 *  4. Viene stabilito il primo giocatore:
 *     - il player che ha scelto Miss Scarlett
 *     - tramite spareggio se non e' stata scelta Miss Scarlett
 */
public class Player implements StateChangeListener, Hooks {

	/**
	 * Logger
	 */
	public static Logger log;
	
	/**
	 * Disabilita il timer (per debug)
	 */
	private final boolean enable= true;

	/**
	 * Riferimento alla finestra di gioco
	 */
	private MainWindow wind;
	/** 
	 * Riferimento alla finestra di registrazione (la finestra iniziale)
	 */
	private CluedoStart regWind;
	/**
	 * Finestra di dialogo per la selezione del colore della pedina
	 */
	private PawnSelection pawnDialog;
	/**
	 * Identifica quale pedina il giocatore ha scelto ad inizio gioco
	 */
	private Pawn pawn;
	/**
	 * Lista delle carte di questo giocatore
	 */
	private List<Card> myCards;
	/**
	 * Colori fra cui il player puo' scegliere.
	 * Quando viene dichiarato l'inizio della partita viene riempito con tutti
	 * i colori disponibili, Successivamente vengono rimossi i colori scelti
	 * dagli altri giocatori. In questo modo rimangono solo i colori fra cui 
	 * questo giocatore puo' scegliere.
	 */
	private List<ColorPawn> colorsAvailable;
	/**
	 * Nickname di questo giocatore (univoco fra tutti i player).
	 * Coincide con quello del transceiver.
	 */
	private String nickname;
	/**
	 * Mappa fra nomi di gruppi di gioco (String) e "transceiver"
	 * del player proprietario (ovvero una map-entry fra nome del transceiver
	 * e il receiver associato)
	 * Usato per la mutua esclusione: non porre a null e non reistanziare.
	 */
	private final HashMap<String, Map.Entry<String, Receiver>> playGroups;
	/**
	 * Nome del gruppo di gioco di cui questo utente e' membro
	 * null se non e' membro di alcun gruppo.
	 */
	private String playGroup;
	
	/**
	 * Nome del proprietario del gruppo di cui il player fa parte
	 */
	private String owner;
	
	/**
	 * Timer per controllare se alcuni player sono andati in crash mentre 
	 * aspettavo che un player compisse un'azione. In particolare serve per 
	 * sapere se e' andato in crash proprio il player da cui si sta attendendo
	 * un'azione.
	 */
	private Timer checkCrashTimer= null;
	
	/**
	 * Timer che gestisce il "rifresco" della registrazione del player e
	 * del gruppo di cui questo player e' proprietario
	 */
	private RefreshTimer refreshTimer;
	
	/**
	 * Membri del gruppo di gioco di cui fa parte il player. La mappa e' vuota se
	 * il player non fa parte di alcun gruppo.
	 * Mappa da nickname dei membri (String) a zombie vivi/morti (Zombie).
	 * 
	 * Quando la partita non e' ancora iniziata:
	 *  - tutti gli zombie sono vivi
	 *  - il primo elemento della lista e' il proprietario del gruppo
	 *  - quando un player si unisce, un nuovo elemento viene aggiunto alla mappa
	 *  - quando un player esce, il corrispondente elemento viene rimosso dalla mappa
	 * 
	 * Quando la partita e' gia' iniziata:
	 *  - gli elementi della mappa vengono "congelati": non possono essere 
	 *    aggiunti o rimossi elementi dalla mappa
	 *  - gli zombie possono essere vivi o morti
	 *  - il primo zombi-vivo della lista e' il proprietario del gruppo
	 *  - quando un player esce, il corrispondente zombie diventa uno zombie-morto
	 * 
	 * Nota che la {@link LinkedHashMap} e' ordinata in base agli inserimenti.
	 * 
	 * Usato per la mutua esclusione: non porre a null e non reistanziare.
	 */
	private final LinkedHashMap<String, Zombie> playGroupMembers;

	/**
	 * Stato del player (vedi diagramma degli stati).
	 * Le transizioni di stato devono essere fatte quando le operazioni che le hanno causate
	 * sono state completate.
	 */
	private PlayerState myState;
	/**
	 * Numero di player partecipanti allo spareggio per il primo giocatore
	 */
	private Integer numTieBreak;
	/**
	 * Il tiro di dado dello spareggio di questo giocatore.
	 * Devo conservare il risultato del mio tiro per poterlo confrontare con
	 * quello degli altri
	 */
	private int myTieBreakDice;
	
	/**
	 * Bisogna ancora fare il primo spareggio
	 */
	private boolean firstTieBreak=true;
	
	/**
	 * Tutti i tiri di dado effettuati per lo spareggio
	 */
	private Vector<Integer> tieBreakDice;
	/**
	 * Receiver del registration server
	 */
	private RmiReceiver regSrv;
	/**
	 * RMI Registry da contattare per fare lookup del Registration Server
	 */
	private Registry registry;
	/**
	 * Hostname del RMI Registry
	 */
	private String registryHost;
	
	// GESTIONE DEI THREAD
	
	/**
	 * Executor che esegue il timer dell'AreYouAlive
	 */
	private Executor timerThread;
	
	/**
	 * Executor per gestire i messaggi inviati e ricevuti
	 */
	private Executor msgThread;
	
	
	/**
	 * Lock che indica se bisogna aspettare o meno a consumare messaggi (usato da
	 * GetAndAssignMessages)
	 */
	private final Lock stopConsuming;
	
	/**
	 * Carte soluzioni del delitto, necessario memorizzarle nel player in quanto la distribuzione delle carte
	 * avviene prima che la MainWindow sia creata
	 */
	private Character character=null;
	private Weapon weapon=null;
	private Room room=null;
	
	/**
	 * Ho barato nel formulare la mia ipotesi
	 */
	private boolean cheat = false;
	
	/**
	 * Contiene le risposte ricevute da ogni player 
	 * nel momento in cui chiede se qualcuno puo' confutare
	 * la sua ipotesi
	 */
	private LinkedHashMap<String,Card> refuteAnswers=null;
	
	/**
	 * Mappa il numero di turno con il corrispondente player che ha risposto ad una confutazione di ipotesi
	 * E' utile, insieme a refuteAnswer per ricavare qual'e' il player precedente, a quello che ha fatto
	 * l'ipotesi, che ha una carta per confutare l'ipotesi
	 */
	private LinkedHashMap<Integer,String> refutePlayerTurn =null;
	
	/**
	 * Transceiver da cui ottenere i messaggi in entrata e attraverso cui spedire quelli in uscita
	 */
	private RmiReliableCausalOrderingMulticast myTrans;

	/**
	 * Crea una nuova istanza di Player. Non effettua effettua la registrazione sul
	 * Registration Server.
	 * @param nickname nickname del player da creare
	 * @param registryHost nome host del RMI Registry dove e' "bindato" il Registration server
	 * @param grpWind la finestra di registrazione dei gruppi (CluedoStart)
	 */
    public Player(String nickname, String registryHost, CluedoStart grpWind) {
    	try {
    		log= new Logger(nickname+".txt");
		} catch (FileNotFoundException e) {
			System.err.println("file di log "+nickname+".txt inaccessibile");
		}
    	
    	if (!enable) log.print("ATTENZIONE: TIMER NON ABILITATO");
    	
    	this.nickname = nickname;
    	this.registryHost= registryHost;
    	
    	pawnDialog= null;
    	regSrv= null;
    	playGroup= null;
    	checkCrashTimer = new Timer(5000, new TimerCheckCrash()); //TODO 5 secondi troppi??pochi???
		refreshTimer= new RefreshTimer();
		
    	timerThread= Executors.newSingleThreadExecutor();
    	msgThread= Executors.newSingleThreadExecutor();
		
    	myTrans=new RmiReliableCausalOrderingMulticast(nickname,this);
    	myTrans.setLogger(log);

    	myState= new PlayerState(this.nickname);
    	myState.addStateChangeListener(this);
    	// istanzio le mappe: serviranno per la mutua esclusione (blocchi synchronized)
		playGroups= new HashMap<String, Map.Entry<String,Receiver>>();
		playGroupMembers= new LinkedHashMap<String, Zombie>();
    	log.printLog("Player: il player "+nickname+" e' appena stato creato");
		setGroupWindow(grpWind);
		
		stopConsuming= new Lock();
		addMsgThread(new GetAndAssignMessages()); //viene messo nella thread pool per migliorarne performance e affidabilita'
    }
    
    
     // METODI PER ASSOCIARE AL PLAYER LA FINESTRA DI GIOCO (MainWindow) E LA FINESTRA DI REGISTRAZIONE (CluedoStart)
  
    /** Imposta la finestra sulla quale i metodi invocati da remoto andranno ad operare,
     *  salva la soluzione nella MainWindow, escludi le mie carte da quelle della soluzione
     *  e visualizza le mie carte nell'interfaccia grafica
     * @param window la MainWindow del player
     */
    private synchronized void setGameWindow(MainWindow window){
    	
    	wind=window;
    	
    	if (wind != null) {
        	log.printLog("Player: mi e' stata assegnata la finestra di gioco (MainWindow)");
        	log.printLog("Player: comunico alla mia finestra di gioco la soluzione");
	    	//comunica alla MainWindow la soluzione
	    	new SetSolutionRunnable(character,weapon,room).run();
	    	
        	log.printLog("Player: segno le carte che ho in mano come scartate dalla soluzione");
	    	// le carte che ho in mano non fanno parte della soluzione
	    	for(Card c: myCards)
			new DiscardCardRunnable(c).run();
	    	
        	log.printLog("Player: visualizzo, nella mia MainWindow, le carte che mi sono state date");
	    	//mostra le mie carte nell'interfaccia grafica
	    	new ShowCardsRunnable((LinkedList<Card>)myCards).run();
	    	
	    	log.printLog("Player: aggiungo i nomi dei player alla nicknamesArea e le relative pedine nel tabellone");
    		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
				Zombie player=entry.getValue();
				// aggiungo il nickname alla nicknames area
				new AddNicknameRunnable(player).run();	
				//e aggiungo la realtiva pedina al tabellone se non e' morto prima
				if(player.isAlive())
					new AddPawnRunnable(player).run();

    		}
    	}
    	else  log.printLog("Player: non ho piu' una finestra di gioco associata");

    }
    
    /** 
     * Imposta la finestra di registrazione associata al player
     * @param window la finestra dei gruppi
     */
    private synchronized void setGroupWindow(CluedoStart window){
    	
    	regWind= window;
    	log.printLog("Player: mi e' stata assegnata la finestra di registrazione CluedoStart)");

    	
    }
    
    
    
    
    //THREAD POOL
    
    /**
     * Aggiunge alla thread pool ed avvia il thread che gestisce i messaggi
     * @oparam thread il thread che gestisce i messaggi
     */
    public void addMsgThread(Runnable thread){
    	
    	msgThread.execute(thread); //viene eseguito il thread passato simultaneamente ad altri thread non ancora terminati
    	
    }
    
    /**
     * Aggiunge alla thread pool ed avvia il thread del timer
     * @oparam thread il thread che gestisce i messaggi
     */
    public void addTimerThread(Runnable thread){
    	
    	timerThread.execute(thread); //viene eseguito il thread passato simultaneamente ad altri thread non ancora terminati
    	
    }
    
    
    //METODI DI REGISTRAZIONE AL REGISTRATION SERVER
    
    /**
     * Registra questo player presso il Registration Server
     * @return true se la registrazione e' andata a buon fine, false altrimenti
     * @throws RemoteException 
     * @throws NotBoundException 
     */
    public synchronized boolean addRegistration() throws RemoteException, NotBoundException, Exception {
    	
		if (myState.isRegistered()) {
			log.printErr("Tentativo di registrazione di questo player, ma era gia' registrato. Operazione annullata.");
    		return false;
    		
    	} else {

			log.print("Ottengo il registro su " + registryHost);
			registry= LocateRegistry.getRegistry(registryHost);
			log.print("Lookup Registration Server \"" + CommonConstants.REGSRV + "\"");
			regSrv = (RmiReceiver) registry.lookup(CommonConstants.REGSRV);
			log.print("Aggiungo la registrazione al Registration Server");
			
    		// aggiungo la mia registrazione creo la mappa dei gruppi di gioco
    		RequestAddPlayer req= new RequestAddPlayer(nickname, myTrans);
    		Message msgResp= myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
    		RequestAddPlayer payResp= (RequestAddPlayer) msgResp.getPayload(); 
    		
    		if (payResp.response) {
    			playGroups.clear();
        		playGroups.putAll(payResp.playGroups);
        		playGroupMembers.clear();
        		log.print("Play groups: " + playGroups.keySet());
        		// aggiungo i giocatori alla finestra di registrazione
        		for (String n : playGroups.keySet()) {
        			EventQueue.invokeLater(new UpdateGroupsList(n, "add"));
        		}
        		myState.register();
        		
        		return true;
    		} else {
    			return false;
    		}
    	}
    }
    
    
    /**
     * Rimuove la registrazione di questo player dal Registration Server
     * ed esegue unexport
     * @throws RemoteException eccezione nell'invocazione remota
     */
    public synchronized void delRegistration() throws RemoteException, Exception {
    	
    	if (!myState.isRegistered()) {
    		log.printErr("Tentativo di rimuovere la registrazione di questo player, ma era gia' non registrato. Operazione annullata.");
    	} else {
    		log.print("Rimuovo la registrazione di questo player dal Registration Server");
    		
    		RequestDelPlayer req= new RequestDelPlayer(nickname);
    		myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
    		
    		myTrans.unexport(); // da questo momento non ricevo piu' chiamate remote
    		playGroups.clear();
    		playGroupMembers.clear();
        	playGroup= null;
        	myState.unregister();
    		// ripulisco la lista dei gruppi e dei membri
    		EventQueue.invokeLater(new UpdateGroupsList());
    		EventQueue.invokeLater(new UpdateMembersList());
    	}
    }
    
    /**
     * Rimuove la registrazione del player dato come parametro dal Registration Server
     * @param z zombie da rimuovere
     * @throws RemoteException eccezione nell'invocazione remota
     */
    public void delRegistration(Zombie z) throws RemoteException, Exception {
    	// nessun controllo: rimozione brutale
    	/*
    	 * Il controllo da fare sarebbe:
    	 * String name= z.getNickname();
    	 * if (!playGroupMembers.get(name).state.isRegistered()) {operazione non permessa}
    	 * else {regSrv.delRegistration(name);}
    	 * 
    	 * Ma tutti gli zombie corrispondenti a player remoti presenti in 
    	 * playGroupMembers, quindi questo controllo e' inutile.
    	 */
    	
		RequestDelPlayer req= new RequestDelPlayer(z.getNickname());
		myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
    }
    
    
    // METODI DI REGISTRAZIONE AL GRUPPO DI GIOCO
    
	public synchronized void notifyAddPlayGroup(String groupName, String ownerName, Receiver ownerTrans)  {
    	// inviato dal Registration Server
    	
    	// gestione della ricezione della notifica
    	
    	if (!playGroups.containsKey(groupName) && !playGroups.containsValue(ownerTrans)) {
			playGroups.put(groupName, new AbstractMap.SimpleEntry<String, Receiver>(ownerName, ownerTrans));
		}
    	
    	log.print("Play groups: " + playGroups.keySet());
		EventQueue.invokeLater(new UpdateGroupsList(groupName, "add"));
	}

	public synchronized void notifyDelPlayGroup(String groupName) {
    	// inviato dal Registration Server
    	
    	// gestione della ricezione della notifica
    	
    	playGroups.remove(groupName);
    	
		log.print("Play groups: " + playGroups.keySet());
		EventQueue.invokeLater(new UpdateGroupsList(groupName, "del"));
	}
    
    public synchronized void notifyChangePlayGroupOwner(String groupName, String newOwnerName, Receiver newOwnerTrans) {
    	// inviato dal Registration Server
    	
    	log.print("E' cambiato il proprietario del gruppo \"" + groupName + "\"");
    	playGroups.put(groupName, new AbstractMap.SimpleEntry<String, Receiver>(newOwnerName, newOwnerTrans));
    }

	@Override
	public void hookJoinGroup(Map<String, Receiver> group) {
		// mi sono appena unito al gruppo => popolo playGroupMembers
		
		log.print("Sono membro del gruppo \"" + playGroup + "\"");
		
		// calcolo chi e' il proprietario
		String owner= group.keySet().iterator().next();
		
		// popolo la mia mappa degli zombie
		playGroupMembers.clear();
		for (Map.Entry<String, Receiver> entry : group.entrySet()) {
			Zombie z;
			String name=entry.getKey();
			Receiver trans= entry.getValue();
			/*
			 * All'interno di "group" c'e' anche il player locale, 
			 * che devo trattare separatamente
			 */
			if (name.equals(getNickname())) {
				// player locale
				z= new Zombie(this, myTrans,playGroupMembers, myState);
			} else {
				// player remoto
				boolean b= name.equals(owner); // true se 'entry' e' il proprietario
				z= new Zombie(this, name, trans, playGroupMembers, b);								
			}
			playGroupMembers.put(name, z);
			// mappa sequenceNumbers gia' popolata
		}
		myState.joinGroup();
		
		System.out.println("Membri del gruppo: " + playGroupMembers.keySet());
		// aggiorno la visualizzazione dell'interfaccia
		EventQueue.invokeLater(new UpdateMembersList());
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			EventQueue.invokeLater(new UpdateMembersList(entry.getKey(), "add"));
		}
	}
	
	@Override
	public boolean hookCanItJoin(String name) {
		if (myState.isGroupOwner() && !myState.isPlayStarted()) {
			// questo player e' proprietario di un gruppo
			// controllo il numero attuale di membri del gruppo
			if (playGroupMembers.size()<CommonConstants.MAX_PLAYERS) {
				return true;
			} else {
				// raggiunto numero massimo di players
				return false;
			}
		} else {
			// questo non e' il proprietario oppure la partita e' gia' iniziata
			return false;
		}
	}
	
	public synchronized void notifyGroupMembers(String memName, Receiver memTrans, boolean joinLeave) {
		// messaggio inviato in multicast
		// questa notifica non viene invocata su chi l'ha generata
		
		/*
		 * - questa notifica viene ricevuta solo se sono gia' membro di un gruppo
		 *   e la partita non e' ancora iniziata
		 * - Gestione della notifica dell'entrata o dell'uscita di un membro dal gruppo
		 * - Nota che il proprietario chiama questa notifica anche su se stesso.
		 * - Questa notifica e' invocata solo quando la partita non e' iniziata,
		 *   questo significa che qui non devo gestire zombie-morti
		 * - Quando arrivo qui, la mappa dei gruppi del transceiver e' gia' stata aggiornata 
		 */
		/*
		 * Nota che non e' possibile che un player si unisca a partita gia' 
		 * iniziata perche' questa operazione viene rifiutata da parte del 
		 * proprietario per mezzo del metodo joinPlayGroup() (restituisce false).
		 */

		if (! (myState.isGroupMember() && !myState.isPlayStarted()) ) {
			throw new RuntimeException("Non puoi invocare notifyGroupMembers() a partita iniziata o se non sei membro di un gruppo. Ignoro la notifica.");
		}
			
		String newOwner, oldOwner;
		oldOwner= this.owner;
		newOwner= myTrans.getFirstName();
		
		if (oldOwner.equals(newOwner)) {
			// il proprietario non e' cambiato
			// sono membro normale oppure proprietario
			
			// la mappa non e' vuota e quindi devo modificare quella esistente
			if (joinLeave) {
				// un membro si e' unito -> inserimento in coda
				playGroupMembers.put(memName, new Zombie(this, memName,memTrans, playGroupMembers, false));
			} else {
				// un membro e' uscito -> lo elimino dalla mappa
				playGroupMembers.remove(memName);
			}
			
		} else {
			// il proprietario del gruppo e' cambiato -> il vecchio proprietario e' uscito
			if (newOwner.equals(nickname)) {
				// io sono il nuovo proprietario
				log.print("Sono il nuovo proprietario del gruppo \"" + playGroup + "\"");
				playGroupMembers.remove(oldOwner); // rimuovo il vecchio proprietario
				this.owner= newOwner;
				
				RequestChangePlayGroupOwner req= new RequestChangePlayGroupOwner(playGroup, nickname, myTrans);
				try {
					myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
				} catch (Exception e) {
					// TODO no tolleranza reg srv
					//e.printStackTrace();
				}
				
				playGroups.put(playGroup, new AbstractMap.SimpleEntry<String, Receiver>(nickname, myTrans));
				myState.gainOwnership();
				// aggiorna la visualizzazione dei bottoni
				EventQueue.invokeLater(new EnableCluedoStartPanels(false, true, true));
			} else {
				// io NON sono il nuovo proprietario
				log.print("Il nuovo proprietario del gruppo e' \"" + newOwner + "\"");
				playGroupMembers.remove(oldOwner); // rimuovo il vecchio proprietario
				Receiver p= playGroupMembers.get(newOwner).getLinkedPlayerTrans();
				this.owner= newOwner;
				playGroups.put(playGroup, new AbstractMap.SimpleEntry<String, Receiver>(nickname, p));
			}
		}
				
		log.print("Membri del gruppo: " + playGroupMembers.keySet());
		// aggiorno la visualizzazione dell'interfaccia
		EventQueue.invokeLater(new UpdateMembersList());
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			EventQueue.invokeLater(new UpdateMembersList(entry.getKey(), "add"));
		}
	}
	
	/**
	 * Metodo che gestisce la creazione del gruppo di cui questo player sara' proprietario
	 * @param groupName e' il nome del nuovo gruppo
	 */
	public synchronized boolean addMyGroup(String groupName) {
		RequestAddPlayGroup req= new RequestAddPlayGroup(groupName, nickname, myTrans);
		Message respMsg= null;
		try {
			respMsg = myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
		} catch (Exception e) {
			// TODO TODO no tolleranza reg srv
			//e.printStackTrace();
		}
		RequestAddPlayGroup respPay= (RequestAddPlayGroup) respMsg.getPayload();
		
		if (respPay.response) {

			this.owner=nickname;
			playGroup=groupName;
			log.print("Creazione del gruppo \"" + playGroup + "\", di cui sono proprietario");
			playGroupMembers.clear();
			playGroupMembers.put(this.nickname, new Zombie(this,myTrans, playGroupMembers, myState));
			log.print("Membri del gruppo: " + playGroupMembers.keySet());
			myState.createGroup();
			EventQueue.invokeLater(new UpdateMembersList(nickname, "add"));
			
			return true;
		} else {
			
			return false;
		}
	}
	
	/**
	 * Chiede al proprietario di un gruppo di potersi unire
	 * @param groupName nome del gruppo a cui si vuole unire
	 */
	public boolean joinThatGroup(String groupName) {

		String ownerName= playGroups.get(groupName).getKey();
		Receiver ownerTrans= playGroups.get(groupName).getValue();
		
		log.print("Chiedo a \"" + ownerName +  
				"\" di potermi unire al gruppo \"" + groupName + "\"");
		playGroup= groupName; // gruppo a cui intendo unirmi
		
		boolean b= myTrans.joinGroup(ownerName, ownerTrans);
		
		if (!b) {
			// la gestione dell'errore e' a carico del metodo chiamante
			log.printErr("Non mi sono potuto unire al gruppo: " +
					"partita iniziata || massimo numero player || non e' proprietario");
			playGroup= null; // non mi sono unito al gruppo
		} else {
			this.owner=ownerName;
			log.print("Mi sono unito al gruppo con successo");
		}
		return b;
	}
	
	/**
	 * Esce dal gruppo di cui questo membro e' proprietario o membro normale.
	 * Se non e' membro di alcun gruppo non esegue alcuna operazione.
	 * Se questo player e' l'unico membro del gruppo (e quindi ne e' anche il
	 * proprietario), il gruppo viene rimosso dal Registration Server.
	 */
	public synchronized void exitMyPlayGroup() {
		if (myState.isGroupMember()) {
			log.print("Sto uscendo dal gruppo \"" + playGroup + "\"");
			// sono membro di qualche gruppo
			if (myState.isGroupOwner()) {
				// questo stesso player e' il proprietario del gruppo da cui vuole uscire
				if (howManyAlive(playGroupMembers)==1) {
					// sono l'ultimo membro del gruppo (il proprietario stesso)
					// rimuovo il gruppo di gioco dal Registration Server
					RequestDelPlayGroup req= new RequestDelPlayGroup(playGroup);
					try {
						myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
					} catch (Exception e) {
						// TODO no tolleranza reg srv
						//e.printStackTrace();
					}
				}
			}
			
			// non faccio piu' parte del gruppo
			playGroup= null;
			playGroupMembers.clear();
			
			myTrans.leaveGroup(); //lascio il gruppo
			
			myState.exitGroup();

			// aggiorno la visualizzazione
			EventQueue.invokeLater(new EnableCluedoStartPanels(true, false, false));
		}
	}
	
	
	
	//ZOMBIE MORTO (PLAYER USCITO DAL GRUPPO)
	
	public synchronized void notifyPlayerExited(String playerName) {
		// messaggio inviato in multicast
		
		/*
		 * Gestione degli zombie.
		 * Questo metodo viene invocato quando un membro del gruppo esce (per
		 * qualsiasi motivo) a partita gia' iniziata. Questo significa che il 
		 * player in questione e' diventato uno zombie-morto.
		 * 
		 * Nota che il gruppo dei transceiver e' gia' stato aggiornato.
		 */
		if(playerName.equals(nickname)) return; //sono uscito io
			

		//multicastNotifyPlayerExited(sender,playerName);

		
		if (!myState.isPlayStarted()) {
			log.printErr("Non puoi invocare notifyPlayerExited() a partita NON iniziata. Ignoro la notifica.");
			return;
		}

		// Qualcuno e' uscito a partita iniziata -> gestione zombie
		log.print("THRILLER! THRILLER NIGHT!");
		log.print("\"" + playerName + "\" e' diventato uno zombie");
		Zombie zombie =playGroupMembers.get(playerName);
		zombie.markAsDead();
		if (myState.isPlaying()) {
			//rimuovo la pedina del giocatore dal tabellone
			EventQueue.invokeLater(new RemovePawnRunnable(playGroupMembers.get(playerName)));
			//aggiorna con l'immagine zombie
			EventQueue.invokeLater(new ZombieNicknameRunnable(playGroupMembers.get(playerName)));
		}
		
		// lo zombie-morto continuo nello stato in cui era, al piu' non e' piu' proprietario (vedi seguito)
		log.print("Zombie del gruppo: " + playGroupMembers.values());
		
		// e' cambiato il proprietario?
		String newOwner, oldOwner;
		newOwner= getGroupOwnerName(createExportedPlayersMap(playGroupMembers));
		oldOwner= this.owner;
		this.owner=newOwner;
		if (!oldOwner.equals(newOwner)) {
			// e' morto il vecchio proprietario
			playGroupMembers.get(playerName).state.loseOwnership();
			if (newOwner.equals(getNickname())) {
				// io sono il nuovo proprietario
				log.print("Sono il nuovo proprietario del gruppo \"" + playGroup + "\"");
				
				RequestChangePlayGroupOwner req= new RequestChangePlayGroupOwner(playGroup, nickname, myTrans);
				try {
					myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
				} catch (Exception e) {
					// TODO no tolleranza reg srv
					//e.printStackTrace();
				}

				playGroups.put(playGroup, new AbstractMap.SimpleEntry<String, Receiver>(nickname, myTrans));
				myState.gainOwnership();
				// aggiorna la visualizzazione dei bottoni
				EventQueue.invokeLater(new EnableCluedoStartPanels(false, false, true));
			} else {
				// io NON sono il nuovo proprietario
				Receiver p= playGroupMembers.get(newOwner).getLinkedPlayerTrans();
				playGroups.put(playGroup, new AbstractMap.SimpleEntry<String, Receiver>(newOwner, p));
				playGroupMembers.get(newOwner).state.gainOwnership();
			}
			
			if (myState.isPlaying()) {
				//arrivo qui se e' cambiato il proprietario e MainWindow e' aperta (cioe' sto giocando)
				// aggiorno la MainWindow
				String pName= playGroupMembers.get(newOwner).getNickname();
				EventQueue.invokeLater(new InfoRunnable(playGroupMembers.get(newOwner),oldOwner,"ChangeOwner"));
				EventQueue.invokeLater(new ChangeOwnerRunnable(getZombie(pName)));
				
			}
		}
		
		if( myState.isPlayStarted()==true){ 
			
			zombie.runDeadZombie(); // eseguo le azioni dello zombie-morto
			
		}
		
		if ( myState.isPlaying() && howManyAlive(playGroupMembers)==1) { 
			// sono rimasto da solo: ho vinto
			winner();
		}
		log.print(); // lasciami: permette uno stdout piu' leggibile
	}

	

	// ZOMBIE MORTO (PLAYER HA FATTO CRASH)
	public synchronized void notifyPlayerCrashed(String playerName) {    	
		// messaggio inviato in multicast
		
		// Qualcuno ha fatto crash -> gestione zombie
			
		Zombie z =playGroupMembers.get(playerName);
		/*
		 * crash nello stato GROUP_MEMBER => viene rimosso da playGroupMembers
		 * crash dopo lo stato GROUP_MEMBER => viene marcato come morto
		 */
		if (z==null || !z.isAlive()) {
			log.print("Chiama multipla di notifyPlayerCrashed()");
			return; 
		}
		
		log.print("THRILLER! THRILLER NIGHT!");
		log.print("\"" + playerName + "\" e' diventato uno zombie (ha fatto crash)");
		
		z.markAsDead(); // contrassegnato come morto.
		log.print("Zombie del gruppo (dopo crash): " + playGroupMembers.values());
		
		/*
		 * Per prima cosa devo trovare l'eventuale nuovo proprietario. Questo
		 * perche' lo zombie-morto associato al player in crash potrebbe dover 
		 * eseguire delle azioni (-> classe Ghost) e per farlo deve sapere
		 * chi e' il nuovo proprietario.
		 * 
		 * Nota che in questo modo la mappa playGroups viene aggiornata prima
		 * che il fantasma dello zombie-morto compia (eventualmente) la sua
		 * azione.
		 * 
		 * Nota inoltre che il Registration Server inviera' una notifica 
		 * notifyChangePlayGroupOwner(), ma non fara' altro che confermare
		 * quando qui fatto: notifyChangePlayGroupOwner() e' idem-potente.
		 */
		
		// e' cambiato il proprietario?
		String newOwner, oldOwner;
		newOwner= getGroupOwnerName(createExportedPlayersMap(playGroupMembers));
		oldOwner= this.owner;
		this.owner=newOwner;
		if (!oldOwner.equals(newOwner)) {
			// e' morto il vecchio proprietario
			playGroupMembers.get(playerName).state.loseOwnership();
			if (newOwner.equals(getNickname())) {
				// io sono il nuovo proprietario
				log.print("Sono il nuovo proprietario del gruppo \"" + playGroup + "\"");

				RequestChangePlayGroupOwner req= new RequestChangePlayGroupOwner(playGroup, nickname, myTrans);
				try {
					myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req);
				} catch (Exception e) {
					// TODO no tolleranza reg srv
					//e.printStackTrace();
				}
				
				playGroups.put(playGroup, new AbstractMap.SimpleEntry<String, Receiver>(nickname, myTrans));
				myState.gainOwnership();
				// aggiorna la visualizzazione dei bottoni
				EventQueue.invokeLater(new EnableCluedoStartPanels(false, false, true));
			} else {
				// io NON sono il nuovo proprietario
				Receiver p= playGroupMembers.get(newOwner).getLinkedPlayerTrans();
				playGroups.put(playGroup, new AbstractMap.SimpleEntry<String, Receiver>(newOwner, p));
				playGroupMembers.get(newOwner).state.gainOwnership();
			}
			
			if (myState.isPlaying()) {
				//arrivo qui se e' cambiato il proprietario e MainWindow e' aperta (cioe' sto giocando)
				// aggiorno la MainWindow
				showNewInfo(playGroupMembers.get(newOwner).getNickname(),oldOwner,"ChangeOwner");
				EventQueue.invokeLater(new ChangeOwnerRunnable(getZombie(newOwner)));
			}
		}
		
		if (myState.isPlaying()) {
			// sto giocando -> MainWindow
			
			//rimuovo la pedina del giocatore dal tabellone
			EventQueue.invokeLater(new RemovePawnRunnable(z));
			//aggiorna con l'immagine zombie
			EventQueue.invokeLater(new ZombieNicknameRunnable(z));
			
			// se e' aperta la finestra di confutazione, la chiudo
			if (myState.iAmRefutingAssumption()) {
				EventQueue.invokeLater(new Runnable() {
					@Override
					public void run() {
						wind.closeRefuteWindow();
					}
				});

			}
		}
		
		if (myState.isWaitingToStart()) {
			// il player e' morto quando ancora la partita non e' iniziata: lo rimuovo dal gruppo
			playGroupMembers.remove(playerName);
			EventQueue.invokeLater(new UpdateMembersList(playerName, "del"));
			EventQueue.invokeLater(new EnableCluedoStartPanels(false, true, true)); 
			log.print("Membri del gruppo: " + playGroupMembers.keySet());
		} else {
			// => myState.isPlayStarted()==true
			// forse uno degli zombie-morti deve compiere un'azione
			// non necessariamente lo zombie appena morto
			for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
				entry.getValue().runDeadZombie();
			} // questo non e' un multicast, anche se puo' sembrarlo
		}
		
		if ( myState.isPlaying() && howManyAlive(playGroupMembers)==1) { 
			// sono rimasto da solo: ho vinto
			winner();
		}
		
		log.print(); // lasciami: permette uno stdout piu' leggibile
	}

		

	
	// METODI PER PREPARATIVI, INIZIO E CONCLUSIONE PARTITA
	
	
	
	/**
	 * Usato dal proprietario del gruppo per dichiarare di voler iniziare una partita.
	 * Viene controllato che questo sia effettivamente proprietario di un gruppo
	 * di gioco e ci sia il numero minimo di player per giocare.
	 * 
	 * Fasi di preparazione: 
	 *  1. Il proprietario distribuisce le carte. Vengono inizializzati
	 *     gli zombie-vivi. Viene lanciata la finestra di selezione pedina.
	 *     Vengono impostate le carte anche sugli zombie-vivi.
	 *  2. Partendo dal proprietario, vengono scelte le pedine. Ogni scelta
	 *     causa l'invocazione di notifyPlayerPawn() su ogni altro player
	 *  3. Eventualmente viene mostrata la finestra con il lancio di dadi per
	 *     stabilire il primo giocatore.
	 * @return true se e' proprietario di un gruppo e c'e' il numero minimo di
	 * player, false altrimenti
	 */
	public synchronized boolean startPlay() {
		
		if (myState.isGroupOwner() && playGroupMembers.size()>=CommonConstants.MIN_PLAYERS) {
			pawn= null;
			distributeCards();
			return true;
		} else {
			return false;
		}
	}
	
	
	/**
	 * Indica che la partita e' terminata
	 */
	public void stopPlay(){
		log.printLog("Player: la partita e' terminata. Vado nello stato END_GAME");
		/* 
		 * Aggiorno lo stato dei miei zombie ed il mio (
		 * che e' in comune con il mio zombie associato) in modo
		 * che siano nello stato END_GAME.
		 */
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			playGroupMembers.get(entry.getKey()).state.endGame();
		}
	}
	

	
	/**
	 * Determina se bisogna fare uno spareggio per determinare il primo giocatore.
	 * Restituisce il numero di player coinvolti nello spareggio, 0 se non e'
	 * necessario alcun spareggio
	 * @return vedi descrizione
	 */
	public int tieBreak() {
		if(numTieBreak==0){ // primo spareggio
			Zombie myZombie= playGroupMembers.get(getNickname());
			return myZombie.tieBreak();
		}
		else return numTieBreak;
	}
	
	/**
	 * Restituisce true se questo player deve partecipare allo spareggio per
	 * il primo giocatore, false altrimenti
	 * @return vedi descrizione
	 */
	public boolean joinTieBreak() {
		Zombie myZombie= playGroupMembers.get(getNickname());
		return myZombie.joinTieBreak(firstTieBreak);
	}
	
	/**
	 * Se bisogna fare lo spareggio per determinare il primo giocatore e questo
	 * player e' fra quelli coinvolti nello spareggio, esegue un tiro di dado
	 * e lo notifica a tutti i player.
	 */
	public void doTieBreak() {
		if (tieBreak()==0) return;
		
		if (joinTieBreak()) {
			// partecipo allo spareggio
			Random rnd= new Random();
			myTieBreakDice= rnd.nextInt(6)+1;
			
			// TODO barare sul tiro di dado
			//if (nickname.equals("a")) myTieBreakDice= 6;
			//else if (nickname.equals("b")) myTieBreakDice= 5;
			//else if (nickname.equals("c")) myTieBreakDice= 6;
			// chiedo in input il valore
			//String str= JOptionPane.showInputDialog("Sono \"" + getNickname() + "\".\n" + "Inserisci il risultato del tiro di dado");
			//myTieBreakDice= Integer.parseInt(str);
			
			if (myTieBreakDice<1 || myTieBreakDice>6) throw new RuntimeException("Tiro di dado non valido: " + myTieBreakDice);
			
			multicastThrowTieBreakDice(nickname, myTieBreakDice);
		}
	}
	
	public synchronized void throwTieBreakDice(String sender, int num) {
		// messaggio inviato in multicast
		
		// gestione della notifica del dado lanciato dal player con nickname "playerName"
		
		
		// nota che possono arrivare notifiche simultanee -> sincronizzo
		log.print("\"" + sender + "\" ha tirato " + num);
		EventQueue.invokeLater(new PawnDialog(sender + " ha tirato " + num));
		numTieBreak--;
		
		tieBreakDice.set(this.memberIndex(sender),num);
		if (!sender.equals(getNickname())) {
			// tiro di qualcun'altro
			playGroupMembers.get(sender).state.waitingTiebreak();
		} else {
			// e' il mio tiro di dado: niente da fare. Vedi doTieBreak()
			// (a parte aggiornare il mio stato)
			myState.waitingTiebreak();
		}
		
		endTieBreak();
	}
	
	/**
	 * Stabilisce se lo spareggio e' stato eseguito da tutti i player. In caso
	 * affermativo esegue l'azione opportuna: iniziare un nuovo spareggio oppure
	 * lanciare la finestra di gioco.
	 */
	private synchronized void endTieBreak() {

		if (numTieBreak==0) {
			firstTieBreak=false;
			int max= Collections.max(tieBreakDice);
			boolean newTieBreak;
			if (tieBreakDice.indexOf(max) != tieBreakDice.lastIndexOf(max)) {
				newTieBreak= true;
			} else {
				newTieBreak= false;
			}
			// tutti hanno tirato il dado dello spareggio
			if (newTieBreak) {
				// nuovo spareggio
				log.print("Nuovo spareggio");
				numTieBreak=Collections.frequency(tieBreakDice, max);
				
				int k=0;
				for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
					Zombie z= entry.getValue();
					if (tieBreakDice.get(k) == max)
						z.mustDoTieBreak(true);
					else 
						z.mustDoTieBreak(false);
					k++;
				}
				
				EventQueue.invokeLater(new PawnDialog("Pareggio! Si ripete il tiro."));
				chooseFirstPlayer();
			} else if (!abortNewPlay()) {
				synchronized (stopConsuming) {
					stopConsuming.lock();
				}
				// si inizia a giocare
				String winner= null;
				int k=0;
				for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
					Zombie z= entry.getValue();
					
					if (tieBreakDice.get(k) == max) {
						// questo giocatore e' il primo
						z.state.wonTiebreak();
						winner= z.getNickname();
					} else {
						// se non ha vinto, ha perso
						z.state.loseTiebreak();
					}
					k++;
				}
				
				if (myTieBreakDice == max) {
					log.print("Io sono il primo giocatore");
				} else {
					log.print("Io NON sono il primo giocatore");
				}
				EventQueue.invokeLater(new PawnDialog("\n" + winner + " ha il primo turno."));
				// finito si inizia a giocare
				firstTieBreak=true;
    			switchToGameWindow();
			} 
		}
	}
	
    /**
     * Imposta la pedina scelta e comunicalo agli altri player.
     * @param Pawn la pedina scelta
     */
    public void setPawn(Pawn pawn){
		// comunico la mia scelta
    	multicastNotifyPlayerPawn(nickname,pawn);
    }

	public synchronized void notifyPlayerPawn(String sender, Pawn playerPawn) {
		// messaggio inviato in multicast
		
		// il player "p" ha scelto la pedina "c": gestisco questa notifica 
		
		
		Zombie z= playGroupMembers.get(sender);
		
		// sono stato io a scegliere la pedina?
		if (sender.equals(nickname)) {
			this.pawn=playerPawn;
			log.print("Mia pedina: " + this.pawn.getName()); // TODO eliminare
		}
		
		// comunico la scelta del player
		z.state.waitingAfterPawn();
		z.setZombiePawn(playerPawn);
		EventQueue.invokeLater(new PawnDialog(sender + ": " + playerPawn.getName()));
		log.print("\"" + sender + "\" ha scelto \"" + playerPawn.getName() + "\"");

		// rimuovo il colore appena scelto
		colorsAvailable.remove(playerPawn.getColor());
		
		// calcolo il player precedente a me
		Zombie prev= getPreviousPlayer(getNickname());
		
		// la notifica e' arrivata dal player precedente a me?
		int num=  ColorPawn.allColors.length - colorsAvailable.size();
		if ( (prev!=null) && (prev.getNickname().equals(sender)) ) {
			// il player precedente a me ha scelto la pedina
			// ora tocca a me farlo!
			myState.choosingMyPawn();
			EventQueue.invokeLater(new PawnDialog(colorsAvailable));
		} else if (num==playGroupMembers.size()) {
			// tutti hanno scelto la loro pedina

			// passo alla selezione del primo giocatore
			// vedi anche setPawn()
			numTieBreak=0;
			firstTieBreak=true;
			chooseFirstPlayer();
		} else {
			// qualcuno deve ancora scegliere la pedina ma non sono io
			getNextPlayer(sender).state.choosingMyPawn();
		}
	}
	
    /**
     * Gestisce la fase di selezione del primo giocatore
     */
    private synchronized void chooseFirstPlayer() {
    	log.print("Selezione del primo giocatore");
    	if (tieBreak()>0) {
	    	// bisogna spareggiare
    		if(firstTieBreak){ //e' il primo tiro per lo spareggio
    			numTieBreak= tieBreak();
    			for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
    				Zombie zombie= entry.getValue();
    				zombie.mustDoTieBreak(false);
    			}
    			
    		}
    		
	    	myTieBreakDice= 0;
	    	tieBreakDice= new Vector<Integer>(6);
	    	for(int i=0;i<6;i++)
	    		tieBreakDice.add(0);
	    	// visualizzo in ogni caso la finestra
	    	EventQueue.invokeLater(new Runnable() {
				@Override
				public void run() {
					pawnDialog.showFirstPlayerDialog();
				}
			});
			
    		// aggiorno tutti gli stati
	    	for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
				Zombie z= entry.getValue();
				if (z.joinTieBreak(firstTieBreak)) {
					z.state.doTieBreak();
				} else {
					if(!z.state.iAmWaitingTieBreak()) /* Mi assicuro che il player non stesse gia' attendendo da prima nel caso 
						in cui vi siano piu' spareggi consecutivi.
					    Questo per evitare di lanciare l'eccezione transizione di stato non permessa 
					    cercando di andare nello stato WAITING_TIEBREAK in cui il player si trova gia'
					    */
						z.state.doNotTieBreak();
				}
				
				// eventuali zombie-morti tireranno immediatamente il dado
			}
    		
		} else if (!abortNewPlay()) {
			// qualcuno ha scelto miss scarlett -> non bisogna spareggiare
			synchronized (stopConsuming) {
				stopConsuming.lock();
			}
			
			log.print("Il primo giocatore e' Miss Scarlett");
    		// aggiorno tutti gli stati
			String msName= null;
			for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
				if (entry.getValue().getPawnColor().equals(ColorPawn.RED)) {
					entry.getValue().state.iAmMissScarlett();
					msName= entry.getValue().getNickname();
				} else {
					entry.getValue().state.thereIsMissScarlett();
				}
			}

    		final String finalMsName= msName; // che brutto modo...
    		EventQueue.invokeLater(new Runnable() {
				@Override
				public void run() {
					pawnDialog.addInfo("\nNon si puo' non cedere\nal fascino di Miss Scarlett...");
		    		pawnDialog.addInfo(finalMsName + " ha il primo turno.");
				}
			});
    		
			if (pawn.getColor().equals(ColorPawn.RED)) {
				log.print("Io sono il primo giocatore");
	    	} else {
	    		log.print("Io NON sono il primo giocatore");
	    	}
			
			switchToGameWindow();
		}
    }
    
    
	/**
	 * Nasconde la finestra di registrazione (CluedoStart) e lancia una nuova
	 * finestra di gioco (MainWindow).
	 */
    public void switchToGameWindow() {
        
    	log.print();
    	log.print("Player: Lancio MainWindow");
       
        /*
         * switchToGameWindow() viene richiamato all'interno del corpo di metodi RMI.
         * Per non far attendere al chiamante remoto il completo avvio della GUI,
         * utilizzo invokeLater().
         */
        EventQueue.invokeLater(new Runnable() {
        	public void run() {
        		// cerco il primo giocatore
        		Zombie firstTurn= null;
        		synchronized (Player.this) {
					for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
						if (entry.getValue().state.isMyTurn()) {
							firstTurn= entry.getValue();
							break;
						}
					}
				}
                // visualizzo la finestra di gioco
                MainWindow wind = new MainWindow(Player.this); // crea e rende visibile la MainWindow
                setGameWindow(wind);

                // se il primo player e' morto, faccio giocare lo zombie-morto
                firstTurn.runDeadZombie();
                
                // riattivo il consumatore
                synchronized (stopConsuming) {
					stopConsuming.unlock();
					stopConsuming.notifyAll();
				}
            }
    	});
    	
    	
    	// TODO la GUI rimane bloccata (freeze) fintanto che non e' completato il caricamento della finestra
        // il tasto continua appare solamente quando la finestra e' stata caricata in modo da impedire che l'utente possa premere il tasto 
        // mentre il tutto e' ancora in caricamento
        
        EventQueue.invokeLater(new Runnable() {
        	public void run() {
        		pawnDialog.showContinueDialog();
        	}
        });
    }
    
    /**
     * Annulla l'avvio della partita a causa dell'uscita di tutti gli altri player
     * tranne quello locale
     * @return true se l'avvio e' stato effettivamente annullato, false altrimenti
     */
	public boolean abortNewPlay() {

		if (howManyAlive(playGroupMembers)>=2) return false;

		log.print();
		log.print("Tutti gli altri player non ci sono piu': torno alla finestra iniziale");
		
    	
		// Riapro la finestra di selezione gruppi
    	
    	// tutti gli altri player se ne sono andati e sono rimasto solo io
    	// aggiorno la lista dei membri
    	removeDeadZombie();
    	
    	if(wind!=null)
    		setGameWindow(null);
    	
    	EventQueue.invokeLater(new EnableCluedoStartPanels(false, true, true));
    	// mostro la finestra di registrazione
    	EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				pawnDialog.showAbortNewPlay();
				pawnDialog.closePawnSelectionDialog();
		    	regWind.showGroupWindow();
	    	}
		});
    	
    	myState.abortNewPlay();
		
		return true;
	}
    
    /**
     * Utilizzato dal proprietario del gruppo per distribuire le carte ai 
     * giocatori. Nota che non controlla che venga invocato effettivamente dal
     * proprietario.
     */
    private void distributeCards() {
    	log.print("Preparo le carte da distribuire");
    	
    	Vector<Card> deck= new Vector<Card>();
    	List<Weapon> wCards= Weapon.allWeapons();
    	List<Character> cCards= Character.allCharacters();
    	List<Room> rCards= Room.allRooms();
    	Weapon wSol;
    	Character cSol;
    	Room rSol;
    	
    	Collections.shuffle(wCards);
    	Collections.shuffle(cCards);
    	Collections.shuffle(rCards);
    	
    	// prendo le carte della soluzione
    	wSol= wCards.get(0);
    	wCards.remove(0);
    	
    	cSol= cCards.get(0);
    	cCards.remove(0);
    	
    	rSol= rCards.get(0);
    	rCards.remove(0);
    	
    	// con le rimanenti faccio un unico mazzo
    	deck.addAll(wCards);
    	deck.addAll(cCards);
    	deck.addAll(rCards);
    	Collections.shuffle(deck);
    	
    	// distribuisco il mazzo cosi' come e'
    	multicastSetDeck(nickname,rSol, wSol, cSol, deck);
    	
    }
    
	public synchronized void setDeck(String sender,
			Room room, Weapon weapon, Character character, List<Card> deck) {
		// messaggio inviato in multicast
		
		log.print("Partita iniziata");
		
		colorsAvailable= new LinkedList<ColorPawn>(Arrays.asList(ColorPawn.allColors));
		EventQueue.invokeLater(new EnableCluedoStartPanels(false, false, true));
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				pawnDialog= regWind.createPawnSelectionDialog();
				pawnDialog.showPawnSelectionDialog();
			}
		});
		
		// gestione carte
		
    	this.room = room;
    	this.character = character;
    	this.weapon=weapon;
		
    	int n= playGroupMembers.size();
    	
    	// prelevo le mie carte
    	myCards= new LinkedList<Card>();
    	int j= memberIndex();
    	int nCards= deck.size();
    	while (j<nCards) {
    		myCards.add(deck.get(j));
    		
    		j= j+n;
    	}
    	log.print("Mie carte: " + myCards);
    	log.print("Soluzione: " + this.room + ", " + this.weapon + ", " + this.character);
    	
    	// gestione dei miei zombie
    	Zombie zombieOwner= null;
    	for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
    		Zombie z= entry.getValue();
    		z.setZombieDeck(room, weapon, character, deck);
    		
    		if (!z.state.isGroupOwner()) {
    			// "z" non e' il proprietario
    			z.state.setDeck();
    		} else {
    			zombieOwner= z;
    		}
    	}
    	zombieOwner.state.allSetDeck();
    	//if (nickname.equals("b")) addOrderedThread(new Runnable() {public void run() {System.exit(1);}});
    }
	
	
  // METODI PER USCIRE DAL GIOCO O DALLA PARTITA
    
    /**
     * Esce dal gioco:
     * <ol>
     * 		<li>esce dal gruppo (se e' membro di un gruppo)
     * 		<li>rimuove la registrazione dal Registration Server
     * 		<li>effettua il dispose della finestra di CluedoStart (aka finestra
     * 		di selezione gruppi)
     * 		<li>termina (<code>System.exit()</code>)
     * </ol>
     * E' possibile invocare questo metodo in qualsiasi stato.
     * Questo metodo non ritorna, perche' invoca <code>System.exit()</code>. 
     */
    public synchronized void exit() {
    	log.print();
		System.out.println("Player: Esco da Cluedo");
		log.printLog("Player: esco da cluedo, adesso chiamo exitMyPlayGroup(), setGameWindow(null) e delRegistration()");
		try {
			exitMyPlayGroup(); //se non sono membro di un gruppo, non esegue niente
			setGameWindow(null);
			delRegistration();
			// chiudo la finestra di registrazione
			//TODO Puo' capitare che venga eseguito prima il System.exit(0) rispetto a regWind.closeGroupWindow();
			//perche' sono in 2 thread diversi e concorrenti ma penso che la chiamata a regWind.closeGroupWindow();
			//possa essere rimossa in quando il system.exit(0) dovrebbe chiudere tutto senza problemi
			EventQueue.invokeLater(new Runnable() {
				@Override
				public void run() {
					regWind.closeGroupWindow();
				}
			});
			
		} catch (Exception e1) {
			log.printErr("Player: Tanto devo uscire... me ne frego delle eccezioni del Registration Server (che e' bene ricordare che non le tollero).");
			//e1.printStackTrace();
		}
		// in ogni caso esco
		System.exit(0);
    }
    
    /**
     * Esce dal gruppo e torna alla finestra CluedoStart:
     * <ol>
     * 		<li>esce dal gruppo di cui e' membro
     *  	<li>mostra la finestra di CluedoStart (aka finestra
     * 		di selezione gruppi)
     * </ol>
     * Vengono eseguite le operazioni sopra descritte solo se 
     * <code>state.isGroupMember()==true</code>.
     */
    public synchronized void returnGroupWind() {
    	
    	log.print();
    	System.out.println("Player: Esco dal mio gruppo e torno a CluedoStart");
		log.printLog("Player: esco dal mio gruppo e torno a CluedoStart, adesso chiamo exitMyPlayGroup() (se non sono gia' nello stato ALONE e poi setGameWindow(null)");;
		
		if (myState.isGroupMember()==true) //se ho formulato una soluzione errata ho gia' chiamato exitMyPlayGroup()
			exitMyPlayGroup();
		
		setGameWindow(null);
		EventQueue.invokeLater(new EnableCluedoStartPanels(true, false, false));
		// mostro la finestra di registrazione
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				regWind.showGroupWindow();
			}
		});

    }

    /**
     * Rimane nel gruppo e torna alla finestra CluedoStart:
     * <ol>
     * 		<li>rimane nel gruppo di cui e' membro
     *  	<li>mostra la finestra di CluedoStart (aka finestra
     * 		di selezione gruppi)
     * </ol>
     * Vengono eseguite le operazioni sopra descritte solo se
     * <code>state.isGameEnded()==true</code>
     */
    public synchronized void returnAgain() {
    	if (myState.isGameEnded()!=true) return;
    	
    	log.print();
    	System.out.println("Player: Rimango nel mio gruppo e torno a CluedoStart");
		log.printLog("Player: rimango nel mio gruppo e torno a CluedoStart, prima pero' rimuovo gli zombie morti, setGameWindow(null) e " +
				" setto il mio stato a GROUP_MEMBER");
    	
    	
    	removeDeadZombie();
		setGameWindow(null);
		/*
		 * In caso la partita sia terminata per tutti (qualcuno ha vinto), tutti i player sono
		 * ancora nello stesso gruppo quini, in caso si debba ricominciare la partita, 
		 * aggiorno lo stato dei miei zombie ed il mio ( che e' in comune con il mio zombie associato) in modo
		 * che siano nello stato corretto GROUP_MEMBER.
		 * Se invece di ricominciare la partita i player sono usciti non c'e' problema perche' 
		 * la mappa playGroupMembers e' stata gia' aggiornata
		 */
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			playGroupMembers.get(entry.getKey()).state.newGame();
		}
    	EventQueue.invokeLater(new EnableCluedoStartPanels(false, true, true));
    	// mostro la finestra di registrazione
    	EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
		    	regWind.showGroupWindow();
	    	}
		});
    }
	
 
	//METODI DI SUPPORTO
	
    /**
     * Restituisce lo stato del player
     * @return vedi descrizione
     */
    public PlayerState getState(){
    	
    	return myState;
    }
    
    /**
	 * Restituisce il colore (istanza della classe Color) della pedina di un giocatore
	 * @param nickname il nickname del player di cui si vuole sapere il colore della pedina
	 * @return il colore della pedina del giocatore
	 */
	public Color getPlayerColor(String nickname){
				
		ColorPawn color=null;
		color = getZombie(nickname).getPawnColor();

	
		Color textColor=null;
		
		
		switch(color){
		case RED:
			textColor=Color.RED;
			break;
		case MAGENTA:
			textColor=Color.MAGENTA;
			break;
		case YELLOW:
			textColor=Color.YELLOW;
			break;
		case GREEN:
			textColor=Color.GREEN;
			break;
		case WHITE:
			textColor=Color.WHITE;
			break;
		case BLUE:
			textColor=Color.BLUE;
			break;
		}
				
		
		return textColor;
		
	}

	/**
	 * Dati due insiemi differiscono per un solo elemento, restituisce
	 * l'elemento non in comune.
	 * E' indifferente se l'insieme piu' grande e' il primo o il secondo.
	 * @param set1 primo insieme
	 * @param set2 secondo insieme
	 * @return l'elemento non comune
	 */
	private <K> K getSingleton(Set<K> set1, Set<K> set2) {

		Set<K> big, small;
		
		if (set1.size()>set2.size()) {
			big= new HashSet<K>(set1);
			small= set2;
		} else {
			big= new HashSet<K>(set2);
			small= set1; 
		}
		big.removeAll(small);
		
		// ora oldMemberNames ha un solo elemento: il player che e' uscito
		if (big.size()!=1) {
			throw new RuntimeException("I due insiemi non differiscono per un solo elemento: " 
					+ set1 + ", " + set2);
		}
		return big.iterator().next();
	}
	
	/**
	 * Data una mappa di zombie, crea la corrispondente mappa degli oggetti
	 * remoti.
	 * Solo gli zombie-vivi hanno un oggetto remoto associato, venendo pertanto
	 * inclusi nella mappa restituita. Gli zombi-morti invece non sono inclusi
	 * @param zombieMap mappa degli zombie
	 * @return mappa degli oggetti remoti
	 */
	private LinkedHashMap<String, Receiver> 
	createExportedPlayersMap(LinkedHashMap<String, Zombie> zombieMap) {
		LinkedHashMap<String, Receiver> expMap= new LinkedHashMap<String, Receiver>();
		Zombie z;
		
		for (Map.Entry<String, Zombie> entry : zombieMap.entrySet()) {
			z= entry.getValue();
			if (z.isAlive()) {
				expMap.put(entry.getKey(), z.getLinkedPlayerTrans());
			}
		}
		
		return expMap;
	}
	
	
	
    /**
     * Restituisce l'insieme dei nomi dei gruppi di gioco
     * @return nomi dei gruppi di gioco
     */
	public Set<String> getPlayGroupNames() {
		if(playGroups!=null)
			return playGroups.keySet();
		else return null;
	}
	
	/**
	 * Ottiene il gruppo di appartenenza del player
	 * @return il nome del gruppo di cui il giocatore fa parte
	 */
	public String getMyPlayGroup(){
		
		return playGroup;
	}
	
	/**
	 * Dato il nome di un gruppo ritorna il tranceiver del
	 * player proprietario del gruppo stesso.
	 * Utilizza la mappa locale &lt;nome gruppo, transceiver del proprietario&gt;
	 * @param groupName e' il nome del gruppo
	 * @return il transceiver del proprietario del gruppo
	 */
	public Receiver getGroupOwner(String groupName){
		return playGroups.get(groupName).getValue();
	}
	

	/**
	 * Dati i membri di un gruppo ritorna il nickname
	 * del player proprietario del gruppo stesso utilizzando la 
	 * mappa &lt;nome gruppo, tranceiver del proprietario&gt; passata
	 * come parametro. Il proprietario e' il primo elemento della mappa
	 * @param groupMembers mappa usata per trovare il proprietario
	 * @return il nickname del proprietario del gruppo, null se la mappa e' vuota
	 */
	public String getGroupOwnerName(LinkedHashMap<String, Receiver> groupMembers){
		String name= null;
		for (Map.Entry<String, Receiver> entry : groupMembers.entrySet()) {
			name= entry.getKey();
			break;
		}

		return name;
	}
	    
    
    /**
     * Restituisce le carte possedute dal giocatore
     * @return le carte in mano al player
     */
    public LinkedList<Card> getMyCards(){
    	
    	return (LinkedList<Card>)myCards;
    }
    

 
    
    /**
     * Restituisce la pedina del giocatore
     * @return la pedina del giocatore
     */
    public Pawn getPawn(){
    	
    	return this.pawn;
    	
    }
    
   
    
	public String getNickname() {
		
		return this.nickname;
	}
	
    
    /**
     * Controlla se questo e' il proprietario del gruppo
     * @return true se e' il proprietario false altrimenti
     */
    public boolean isGroupOwner(){
    	
    	return myState.isGroupOwner();
    	
    }

    /**
     * Controlla se questo e' membro di un gruppo
     * @return true se e' il proprietario false altrimenti
     */
    public boolean isGroupMember(){
    	
    	return myState.isGroupMember();
    	
    }
    
    
    /**
     * Controlla se e' il turno del giocatore
     * @return true se e' il turno del giocatore false altrimenti
     */
   
    public boolean isMyTurn(){
    	
    	return myState.isMyTurn();
    	
    }
    
    

    @Override
    public String toString() {
    	return nickname;
    }

    
    
	public synchronized void sendInfo(String playerName, String info, String type){
		
			
			log.printLog("Player: (sendInfo) Ho ricevuto un messaggio che e' stato inviato con i seguenti parametri:\n" +
						"playerName: " + playerName+"\n"+
						"info: " +info +"\n"+
						"type: "+ type+ "\n\n");
			
			showNewInfo(playerName, info, type);

	}
	
	
	/**
	 * Mostra una nuova info nell'interfaccia grafica 
     * @param playerName: il player che ha inviato l'informazione (mittente del messaggio)
     * 		oppure il player che ha confutato la mia ipotesi se il parametro type e' "Refuted" 
	 * 		oppure il nuovo proprietario del gruppo se il parametro type e' "ChangeOwner"
	 * 		oppure il player a cui ho confutato l'ipotesi se il parametro type e' "RefuteSent"
	 * 		oppure null.
     * @param msg informazione supplementare che ha un certo significato in base al parametro type
	 * @param type tipo di informazione (usato per creare le informazioni)
	 */
	public synchronized void showNewInfo(String playerName,String info,String type){
		
		log.printLog("Player: mostro l'info ricevuta nella mia area info. i parametri di showNewInfo() sono:\n" +
				"playerName: " + playerName+"\n"+
				"info: " +info +"\n"+
				"type: "+ type);
		
		EventQueue.invokeLater(new InfoRunnable(getZombie(playerName), info, type));
		
	}
    
    
	public synchronized void sendChatMessage(String sender,String msg) {
    	
			
			log.printLog("Player: (sendChatMessage) Ho ricevuto un messaggio di chat che e' stato inviato con i seguenti parametri:\n" +
					"sender: " + sender +"\n"+
					"msg: " + msg);			
			
			log.printLog("Player: Aggiorno la mia chat con il messaggio appena ricevuto");
			
			// gestione della ricezione di un messaggio di chat da un player remoto o di me stesso
			EventQueue.invokeLater(new ChatMessageRunnable(sender,msg,myState.isPlaying()));
		
		
    }
    
    
    
	//METODI DI GESTIONE DEGLI ZOMBIE
	
	/**
	 * Restituisce quanti zombie sono vivi nella mappa data
	 * @param map mappa di cui calcolare quanti zombie sono vivi
	 * @return numero di zombie vivi
	 */
	private int howManyAlive(LinkedHashMap<String, Zombie> map) {
		int n=0;
		for (Map.Entry<String, Zombie> entry : map.entrySet()) {
			if (entry.getValue().isAlive()) n++;
		}
		return n;
	}
	
	/**
	 * Dato il nome di un gruppo ritorna lo zombie associato all'oggetto remoto proprietario del gruppo
	 * @param groupName e' il nome del gruppo
	 * @return vedi descrizione
	 */
	public Zombie getZombieGroupOwner(String groupName){
		
		String name= playGroups.get(groupName).getKey();
		return getZombie(name);

	}
	
	
	/**
	 * Ottiene lo zombie associato ad un player o null se non c'e' alcuno zombie associato
	 * @param nickname il nickname del player di cui si vuole lo zombie corrispondente
	 * @return vedi descrizione
	 */
	public Zombie getZombie(String nickname){
	
		if(playGroup==null) return null;
		
		return playGroupMembers.get(nickname);
	}
		
	
	/**
	 * Restituisce il primo elemento della mappa playGroupMembers. Questo 
	 * rappresenta il proprietario del gruppo al momento dell'inizio del gioco.
	 * Successivamente il proprietario puo' cambiare, ma il primo proprietario
	 * rimane lo stesso
	 * @return vedi descrizione
	 */
	public Zombie getFirstZombieGroupOwner(){
		
		Zombie z= null;
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			z= entry.getValue();
			break;
		}

		return z;
	}
	
	
	
	/**
	 * Restituisce la posizione di questo player all'interno del gruppo di gioco (usando 0 come prima
	 * posizione). Questa posizione non coincide con l'indice del turno di gioco
	 * (ad es. un valore restituito pari a 0 non significa che e' il primo a
	 * giocare). Si tratta invece della posizione in cui questo player si e' unito
	 * al gruppo.
	 * In altre parole l'indice che occupa il player all'interno di playGroupsMembers
	 * @return la posizione del playernel gruppo di gioco, -1 se il nickname non e' presente in playGroupsMembers
	 */
	private int memberIndex() {
		return memberIndex(getNickname());
	}
 
	/**
	 * Restituisce la posizione del player passato come parametro all'interno del gruppo di gioco (usando 0 come prima
	 * posizione). Questa posizione non coincide con l'indice del turno di gioco
	 * (ad es. un valore restituito pari a 0 non significa che e' il primo a
	 * giocare). Si tratta invece della posizione in cui questo player si e' unito
	 * al gruppo.
	 * In altre parole l'indice che occupa il player all'interno di playGroupsMembers
	 * @return l'indice del turno del giocatore, -1 se il nickname non e' presente in playGroupsMembers
	 */
	private int memberIndex(String name) {
		int ret= 0;
		boolean found= false;
		
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			if (entry.getKey().equals(name)) {
				// trovato
				found= true;
				break;
			} else {
				ret++;
			}
		}
		
		if (found) return ret;
		else return -1;
	}
	
	/**
	 * Restituisce lo zombie successivo a quello passato come parametro
	 * @param name nickname del player da prendere come riferimento
	 * @return zombie successivo, null se e' l'ultimo
	 */
	public synchronized Zombie getNextPlayer(String name) {
		Zombie next= null;
		boolean found= false;
			
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			if (entry.getKey().equals(name)) {
				// trovato
				found= true;
				// faccio un'altra iterazione
			} else if (found) {
				// qui arrivo l'iterazione successiva
				next= entry.getValue();
				break;
			}
		}
			
		return next;
	}
	
	/**
	 * Restituisce lo zombie precedente a quello passato come parametro, null
	 * se e' il primo
	 * @param name nickname del player da prendere come riferimento
	 * @return zombie del player precedente, null se e' il primo
	 */
	public synchronized Zombie getPreviousPlayer(String name) {
		Zombie prev= null;
			
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			if (entry.getKey().equals(name)) {
				// trovato
				break;
			} else {
				prev= entry.getValue();
			}
		}
			
		return prev;
	}
	
    
    /**
     * Restituisce il nome del player che ha il turno
     * @return vedi descrizione, null se nessuno ha il turno
     */
    public synchronized String whoseTurnIsIt() {
    	for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
        	if (entry.getValue().state.isMyTurn()) {
        		return entry.getKey();
        	}
        }
    	return null;
    }


    /**
	 * Rimuove gli zombie morti dal gruppo nella finestra di registrazione 
	 * e dalla mappa playGroupMembers
	 */
	public synchronized void removeDeadZombie(){
		
		log.printLog("Player: rimuovo gli eventuali zombie morti dalla mappa playGroupMembers e dal mio gruppo nella finestra di registrazione");
		Iterator<Map.Entry<String,Zombie>> iter = playGroupMembers.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry<String,Zombie> entry = iter.next();
			if(!entry.getValue().isAlive()){
				String zombieNickname=entry.getKey();
				iter.remove();
				EventQueue.invokeLater(new UpdateMembersList(zombieNickname, "del")); // rimuove il nickname dello zombie morto
			}
		}
		
	}
	
	
	
	// MULTICAST
	
	/**
	 * Invia un messaggio di chat agli altri membri del gruppo richiamando il metodo remoto sendChatMessage() 
	 * @param sender il mittente del messaggio
	 * @param msg messaggio da inviare
	 */
	public synchronized void multicastChatMessage(final String sender, final String msg) {
		
			log.printLog("Player: (multicastChatMessage) Sto facendo un multicast con i seguenti parametri:\n" +
				"sender: " + sender +"\n"+
				"msg: " + msg);
			
			NotifyChatMessage payload= new NotifyChatMessage(msg);
			
			myTrans.rcoMulticast(payload);
							
	}
	
	
	/**
	 * Invia agli altri player la notifica di spostamento della pedina del player attuale
	 * @param playerName il player di cui si deve spostare la pedina
	 * @param x la coordinate x della cella in cui si e' spostata la pedina
	 * @param y la coordinata y delle cella in cui si e' spostata la pedina
	 */
	public synchronized void multicastMovePawn(final String playerName, final int x, final int y) {
		
			log.printLog("Player: (multicastMovePawn) Sto facendo un multicast con i seguenti parametri:\n" +
				"playerName: " + playerName +"\n"+
				"x: " + x +
				"y: " + y);
					
			NotifyMovePawn payload= new NotifyMovePawn(playerName,x,y);
			
			myTrans.rcoMulticast(payload);
		
		
	} 
	
	
	/**
	 * Invia l'ipotesi formulata a tutti gli altri membri del gruppo richiamando
	 * il metodo checkAssumption() 
	 * @param sender il mittente del messaggio
	 * @param ch la carta personaggio
	 * @param we la carta arma
	 * @param rm la carta stanza
	 */
	public synchronized void multicastCheckAssumption(final String sender,final Character ch, final Weapon we, final Room rm) {
		
			log.printLog("Player: (multicastCheckAssumption) Sto facendo un multicast con i seguenti parametri:\n" +
				"sender: " + sender +"\n"+
				"ch: " + ch+"\n"+
				"we: " + we+"\n"+
				"rm: " + rm);
		
			if(sender.equals(nickname)) //sono io che ho fatto l'ipotesi
				for(Card c: myCards){ //controllo se ho barato
					if(c.equals(ch) || c.equals(we) || c.equals(rm)){
						cheat = true;
						log.printLog("Player: Ho iniziato io questo multicast (multicastCheckAssumption) e ho barato nel formulare l'ipotesi");
						break;
					}
				}	
			
			NotifyCheckAssumption payload= new NotifyCheckAssumption(ch,we,rm);
			
			myTrans.rcoMulticast(payload);
		
	}
	
	
	/**
	 * Comunica a tutti i membri del gruppo (compreso se stesso) se il player puo' confutare l'ipotesi oppure no
	 * @param sender il mittente del messaggio
	 * @param card la carta che confuta l'ipotesi oppure null se non posso confutare l'ipotesi
	 */
	public synchronized void multicastRefuteAssumption(final String sender,final Card card) {
		
		
			log.printLog("Player: (multicastRefuteAssumption) Sto facendo un multicast con i seguenti parametri:\n" +
				"sender: " + sender +"\n"+
				"card: " + card);
						
			NotifyRefuteAssumption payload= new NotifyRefuteAssumption(card);
			
			myTrans.rcoMulticast(payload);
		
		
	}
	
	/**
	 * Invia a tutti i membri del gruppo (compreso se stesso) la notifica che il player ha terminato il turno
	 * @param sender il mittente del messaggio
	 * @param lost true se ho formulato una soluzione errata ed ho perso (quindi passo il turno e poi esco), false se passo semplicemente il turno
	 */
	public synchronized void multicastEndTurn(final String sender,final boolean lost) {
		
			log.printLog("Player: (multicastEndTurn) Sto facendo un multicast con i seguenti parametri:\n" +
				"sender: " + sender +"\n"+
				"lost: " + lost);
			
			NotifyEndTurn payload= new NotifyEndTurn();
			
			myTrans.rcoMulticast(payload);
			
			
		  				
			if(lost){ //ho formulato una soluzione errata
			
				log.printLog("Sono all'interno del multicast (multicastEndTurn), ho gia' inviato tutti i messaggi\n" +
						"lost=true indica che ho formulato una soluzione sbagliata. Chiamo exitMyPlayGroup()");
				
	
				exitMyPlayGroup();
	
			}
		
		
	}
	
	/**
	 * Invia a tutti i membri del gruppo (incluso il player che ha vinto) la notifica della vittoria di un player
	 * @param winner il vincitore
	 */
	public synchronized void multicastWinPlayer(final String winner) {
			
			
			log.printLog("Player: (multicastWinPlayer) Sto facendo un multicast con i seguenti parametri:\n" +
				"winner: " + winner +"\n");
		
			
			NotifyWinner payload= new NotifyWinner(winner);
			
			myTrans.rcoMulticast(payload);
				
	}
	
	
	/**
	 * Invia a tutti i membri del gruppo (incluso se stesso) un'informazione di gioco
	 * @param playerName il player che ha inviato il messaggio
	 * 		oppure il player che ha confutato la mia ipotesi se il parametro type e' "Refuted" 
	 * 		oppure il nuovo proprietario del gruppo se il parametro type e' "ChangeOwner"
	 * 		oppure il player a cui ho confutato l'ipotesi se il parametro type e' "RefuteSent"
	 * 		oppure null.
	 * @param info l'informazione da inviare
	 * @param type il tipo di informazione inviata
	 */
	public synchronized void multicastSendInfo(final String playerName, final String  info, final String type) {
		
			
			log.printLog("Player: (multicastSendInfo) Sto facendo un multicast con i seguenti parametri:\n" +
				"playerName: " + playerName+"\n"+
				"info: " + info+"\n"+
				"type: " + type+"\n\n");
						
			NotifyInfo payload= new NotifyInfo(playerName,info,type);
			
			myTrans.rcoMulticast(payload);
		
	}
	
    /**
	 * Invia a tutti i membri del gruppo (incluso se stesso) la notifica "notifyPlayerPawn()"
	 * @param sender mittente del messaggio
	 * @param playerPawn la pedina scelta
	 */
    public synchronized void multicastNotifyPlayerPawn(final String sender,final Pawn playerPawn) {
		
    	NotifyPlayerPawn payload= new NotifyPlayerPawn(playerPawn);
		
    	myTrans.rcoMulticast(payload);
    }
    
    /**
   	 * Invia a tutti i membri del gruppo (incluso se stesso) il messaggio "setDeck()"
   	 * @param sender mittente del messaggio
   	 * @param room la carta della stanza della soluzione
	 * @param weapon la carta arma della soluzione
	 * @param character la carta personaggio della soluzione
	 * @param deck le altre carte (mescolate) del mazzo
   	 */
    public synchronized void multicastSetDeck(final String sender, final Room room, 
    		final Weapon weapon, final Character character, final List<Card> deck) {
		
    	NotifySetDeck payload= new NotifySetDeck(room,weapon,character,deck);
		
    	myTrans.rcoMulticast(payload);
    }
	

   /**
   	 * Invia a tutti i membri del gruppo (incluso se stesso) il messaggio "throwTieBreakDice()"
   	 * @param sender mittente del messaggio
   	 * @param num il valore di dado tirato
   	 */
    public synchronized void multicastThrowTieBreakDice(final String sender, final int num) {
		
    	NotifyThrowTieBreakDice payload= new NotifyThrowTieBreakDice(num);
		
    	myTrans.rcoMulticast(payload);
    }
	
	
 // METODI DI GIOCO
	
	
	public ColorPawn getPawnColor(){
		return pawn.getColor();
			
	}
	
	 public synchronized void movePawn(String sender, String playerName,int x,int y){
					
			if(sender.equals(nickname))
				return;   	//Sono io il mittente, ho gia' aggiornato la posizione della pedina.

			log.printLog("Player: aggiorno la posizione della pedina di "+playerName);
			
			EventQueue.invokeLater(new MovePawnRunnable(getZombie(playerName),x,y));
		  	
	 }
	 
	 
    public synchronized void winner(String sender, String winner){
    	
    		boolean onlyTwoAlive=false;
    	
    		if(howManyAlive(playGroupMembers)==2) //siamo rimasti in 2 player
    			onlyTwoAlive=true;
    		
			log.printLog("Chiamo stopPlay()");
			stopPlay();
						
			if(winner.equals(nickname)){ // ho vinto io
				log.printLog("Player: ho vinto io, apro la finestra di vittoria");
				if(onlyTwoAlive)
					EventQueue.invokeLater(new EndWindRunnable(getZombie(nickname),"winner",true));
				else
					EventQueue.invokeLater(new EndWindRunnable(getZombie(nickname),"winner",false));
			}
			else{// ho perso
				log.printLog("Player: ho perso io, apro la finestra di sconfitta");
				if(onlyTwoAlive && sender.equals(nickname))
					EventQueue.invokeLater(new EndWindRunnable(getZombie(winner),"wrong",true));
				else
					EventQueue.invokeLater(new EndWindRunnable(getZombie(winner),"loser",false)); //creo la mia finestra perdente

			}
    	
    }
	
	
	/**
	 * Chiamato nel caso in cui ho vinto perche' sono rimasto l'unico in gioco
	 */
	private void winner(){
		log.printLog("Player: sono rimasto l'unico in gioco, ho vinto, chiamo stopPlay() e creo la finestra vincitore");
		stopPlay();
		EventQueue.invokeLater(new EndWindRunnable(getZombie(nickname),"alone",false));
	}
	
	
	/**
	 * Chiamato quado il player ha formulato una soluzione errata
	 */
	public synchronized void wrongSolution(){
		
		log.printLog("Player: La soluzione che ho dato e' errata, ho perso.\n" +
				"Chiamo stopPlay()\n");
		
		if(howManyAlive(playGroupMembers)!=2){

			log.printLog("Player: i player vivi rimasti in gioco sono piu' di 2, adesso invio il multicastSendInfo() " +
					"per comunicare agli altri che ho perso e poi invio il multicastEndTurn() per comunicare che ho " +
					"terminato il turno");
			
			multicastSendInfo(nickname,"","Loser");
			
			multicastEndTurn(nickname,true); //passo il turno (true indica che ho perso)
			
			log.printLog("Player: creo la mia finetra perdente");
			
			EventQueue.invokeLater(new EndWindRunnable(null, "wrong", false));
				
		}
		else{ //se sono rimasti 2 player non devo passare il turno (la partita e' finita)
			
			log.printLog("Player: siamo rimasti in 2 player vivi, adesso invio il multicastSendInfo() " +
					"per comunicare all'altro player che lui ha vinto");
			
			multicastSendInfo(nickname,"","LoserPlayAgain");
			
			Zombie winner=null;
				
			
			
			for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
				Zombie z=entry.getValue();
				if((z.getNickname()!=nickname) && (z.isAlive())){ //ho trovato l'altro player rimasto in vita
					winner=z;
					log.printLog("Player: faccio multicastWinPlayer per comunicare la vittoria all'altro player rimasto in vita");
					multicastWinPlayer(winner.getNickname());
					break;
				}
			}
			
		}
		
	}

       
    public synchronized void endTurn(String sender){
    	
    	/* la riga seguente e' utile per evitare l'eccezione nel seguente scenario:
    	 * - sono rimasti due player vivi
    	 * - quello che ha il turno esce o fa crash
    	 * - a questo punto lo zombie morto che ne ha preso il posto fa l'endTurn
    	 * - nel frattempo pero' all'altro giocatore rimasto e' stato comunicato che ha vinto ed e' nello stato END_GAME
    	 * - quando gli arriva l'endTurn lancia un'eccezione perche' da END_GAME non puo' andare in MY_TURN
    	*/
    	
    	
			if(myState.isGameEnded() || (!myState.isGroupMember())) return;	
			
			if(getZombie(sender).state.iAmWaitingRefute()){
				log.printLog("Player: (endTurn) il mio zombie "+sender+" era nello stato WAITING_REFUTE e mi e' arrivato l'endTurn del player associato" +
						"\nLo metto nello stato MY_TURN");
				getZombie(sender).state.getAnswers();
			}
    	
			Zombie nextPlayer=null;
   
				
			String tmpName=null;
			boolean found=false;
       
			Zombie first=null;    	
    	    	
			
			log.printLog("Player: (endTurn) cerco a chi e' il prossimo a cui passare il turno");
			
			//Viene controllato chi e' il prossimo  a cui passare il turno
			for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
				Zombie p=entry.getValue();
				tmpName=p.getNickname();
    	
        		
				if(first==null) first=p; //memorizzo il primo della lista dei membri
    				 
				if(found==true){ //ho trovato il player che ha il prossimo turno  					
					nextPlayer=p;
					log.print("Player: E' il turno di "+ tmpName);
					break;
				}
        		
				if(tmpName.equals(sender)){ // trovato la posizione di chi aveva il turno
					found=true;
					continue;
				}		
    					 
			}	
    		
			if(found==true && nextPlayer==null) //chi ha il prossimo turno e' il primo della lista dei membri
				nextPlayer=first;
        	
			tmpName= nextPlayer.getNickname();
   
        	
			// fine del turno del player precedente
			
        	if((sender).equals(nickname)) //sono io che ho terminato il turno
        		log.printLog("Player: sono io che ho terminato il turno, " +
            			"vado nello stato NOT_MY_TURN");
        	else
        		log.printLog("Player: il player "+sender+" mi ha comunicato che ha terminato il turno " +
            			"metto il mio zombie associato a "+sender+" nello stato NOT_MY_TURN");
        	
        	/* N.B: se sono e' lo zombie associato a me che viene recuperato con playGroupMembers.get(sender)
        	 * tutto funziona bene perche' lo state dello zombie e' un riferimento allo stesso stato
        	 * puntato da myState
        	 * */
        	playGroupMembers.get(sender).state.endTurn();
        	
			EventQueue.invokeLater(new ChangeTurnRunnable(getZombie(nextPlayer.getNickname())));
			
			if(this.nickname.equals(tmpName)){ //e' il mio turno
				log.printLog("Player: e' il mio turno, vado nello stato MY_TURN");
				myState.myTurn();
				log.print("Player: E' il mio turno");
			} else { // e' il turno di un altro player
				log.print("Player: e' il turno di "+tmpName);
				log.printLog("Player: e' il turno di "+tmpName+". Metto il mio zombie "+tmpName+" nello stato MY_TURN");
				playGroupMembers.get(tmpName).state.myTurn();
			}
    		
			log.printLog("Player: aggiorno il cambio turno nella mia interfaccia grafica");

    }
	
	public synchronized void checkAssumption(String sender,Character character,Weapon weapon, Room room){
  		
		
			log.printLog("Player: (checkAssumption) Ho ricevuto un messaggio multicast che e' stato inviato con i seguenti parametri:\n" +
					"sender: " + sender +"\n"+
					"character: " + character +"\n"+
					"weapon: " + weapon +"\n"+
					"room: " + room);
	
			if(sender.equals(nickname)){
				return; /* sono gli altri che devono controllare la mia ipotesi */
			}
			
			log.printLog("Player: (checkAssumption) Ho ricevuto l'ipotesi di "+sender+" e vado nello stato REFUTE_ASSUMPTION");
			
			myState.getAssumption(); //ho ricevuto un'ipotesi per controllare se posso confutarla
			
			synchronized (playGroupMembers){
	    		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
	    		
	    			Zombie z = entry.getValue();
	    			if((!z.getNickname().equals(sender)) && (!z.getNickname().equals(nickname))){	
	    				/* se e' lo zombie associato a chi ha inviato l'ipotesi
	    				 * non va nello stato REFUTE_ASSUMPTION per controllare se puo' confutarla 
	    				 * Se invece e' il mio zombie non devo fare niente perche' il suo stato e' stato gia' aggiornato
	    				 * con myState.getAssumpotion();
	    				 * */
	    				
	    				log.printLog("Zombie: Il mio zombie " +z.getNickname()+" ha ricevuto l'ipotesi di " +sender+
	    						" e va nello stato REFUTE_ASSUMPTION");
	    				z.setAssumption(nickname, character, weapon, room);
	    				z.state.getAssumption();		
	    			}
	       		}
	    	}
			
			
			log.printLog("Player: (checkAssumption) metto il mio zombie "+sender+ " nello stato WAITING_REFUTE");

			getZombie(sender).state.sendAssumption();
			
		
			//controllo se posso rispondere con qualche carta
			if(myCards.indexOf(character)!=-1 || myCards.indexOf(weapon)!=-1 || myCards.indexOf(room)!=-1){
			
			//posso confutare l'ipotesi, visualizzo la finestra che mi fa selezionare la carta da inviare
				log.printLog("Player: (checkAssumption) posso confutare l'ipotesi. Apro la finestra di confutazione ipotesi");
				
				EventQueue.invokeLater(new RefutWindowRunnable(getZombie(sender),character,weapon,room));
		
			
			}	 
			else{ //non posso confutare l'ipotesi
	
				log.printLog("Player: (checkAssumption) NON posso confutare l'ipotesi di "+ sender +". Invio un multicastRefuteAssumption() per" +
						" comunicare che non posso confutare l'ipotesi");

				/* viene usato un altro thread al fine di impedire la creazione di deadlock tra
			 	multicastCheckAssumption e multicastSendInfo/multicastEndTurn nel player che ha chiesto che venga
			 	controllata la sua ipotesi.
			 	Infatti cosi' facendo questo metodo (checkAssumption) termina subito e viene liberato il lock
			 	su playGroupMembers tenuto da multicastCheckAssumption*/
				multicastRefuteAssumption(nickname,null);
								
				System.out.println("Player: Non posso confutare l'ipotesi di "+sender);

			}					
				
    }
	
	
	public synchronized void refuteAssumption(String sender,Card card){
    	//un giocatore ha confutato un'ipotesi appena fatta
    	
    	

			log.printLog("Player: (refuteAssumption) Ho ricevuto un messaggio multicast che e' stato inviato con i seguenti parametri:\n" +
					"sender: " + sender +"\n"+
					"card: " + card);
			
			if(sender.equals(nickname)) {
				myState.answerRefute(); //ho cominicato a chi ha fatto l'ipotesi se posso confutarla oppure no
				log.printLog("Player: ho risposto all'ipotesi e sono tornato nello stato NOT_MY_TURN");
			}
			else if (!getZombie(sender).state.isNotMyTurn()){
				getZombie(sender).state.answerRefute();
				log.printLog("Player: Il player "+sender+" ha risposto all'ipotesi e io ho messo il mio zombie " +sender+" nello stato NOT_MY_TURN");
			}
			
			if((refuteAnswers==null)  &&  (refutePlayerTurn==null)) //non sono stato io a fare l'ipotesi
				return;
			
			
    	
			if(card==null)
				log.printLog("Player: "+sender+" non puo' confutare la mia ipotesi");
		
			int memberIndex=-1;
    	
			memberIndex = memberIndex(sender);

		
			if(memberIndex!=-1){
				refuteAnswers.put(sender, card);
				refutePlayerTurn.put(memberIndex, sender);
			}
    	
    	
			boolean refuteFound=false;
			boolean allAnswer=false;
    	
    	
			if(memberIndex!=-1 && (refuteAnswers.size() == (playGroupMembers.size()-1))){ //tutti mi hanno risposto 
  
				myState.getAnswers();
				log.printLog("Ho ottenuto tutte le risposte alla mia ipotesi e sono tornato nello stato MY_TURN");
				
				EventQueue.invokeLater(new Runnable(){
					public void run(){
						wind.setIsAssumptionChecked(true);
					}
				});
				
				allAnswer=true;
				int nextIndex=memberIndex(getNickname());
    			Zombie nextPlayer=null;
    			Card nextCard=null;
    		
    			for(int i=0;i<refutePlayerTurn.size();i++){
    			
    				if(nextIndex==(refuteAnswers.size()))
    					nextIndex=0;
    				else
    					nextIndex=nextIndex+1;
    			
    				Zombie pl= getZombie(refutePlayerTurn.get(nextIndex));
    				Card c = refuteAnswers.get(pl.getNickname());
    			
    				if(c!=null){
    					nextPlayer=pl;
    					nextCard=c;
    					refuteFound=true;
    					break;
    				}
  
    			}
    		
    		
    			if (refuteFound) { // ho trovato chi confutera' la mia ipotesi
    				
    				log.printLog("Player: Tutti mi hanno risposto, la mia ipotesi e' stata confutata da " +nextPlayer.getNickname());
    				
    				
    				String type = null;
		    	
    				if(nextCard instanceof Character)
    					type="character";
    				else if(nextCard instanceof Weapon)
    					type="weapon";
    				else if(nextCard instanceof Room)
    					type="room";
			
    				String nextPlayerName=nextPlayer.getNickname();
    				
    				if(nextPlayer.isAlive()){ //non e' uno zombie morto
    				
	    				log.printLog("Player: invio un msg con payload NotifyInfo al player che mi ha confutato l'ipotesi per farglielo sapere");
	    				NotifyInfo notify= new NotifyInfo(nickname, ""+nextCard.getName(),"RefuteSent");
	    				try {
							myTrans.sendAndDeliver(nextPlayerName, nextPlayer.getLinkedPlayerTrans(), notify);
						} catch (Exception e) {
							// TODO gestire eccezione
							e.printStackTrace();
						}
    				
    				}
    				log.printLog("Player: invio sendInfo() in multicast per comunicare a tutti che la mia ipotesi era sbagliata");
    				//comunica a tutti che la tua ipotesi non era corretta
    				multicastSendInfo(nickname,"","WrongAssumption");
		    	
    				log.printLog("Player: apro la finestra che mi mostra chi mi ha confutato l'ipotesi e con che carta");

    				//fai apparire la finestra che mi dice chi mi ha confutato l'ipotesi e con che carta
    				if(type.equals("character"))
    					EventQueue.invokeLater(new ResultAssumptionRunnable(true,getZombie(nextPlayerName),new Character(nextCard.getName()),false));
    				else if(type.equals("weapon"))
    					EventQueue.invokeLater(new ResultAssumptionRunnable(true,getZombie(nextPlayerName),new Weapon(nextCard.getName()),false));
    				else if(type.equals("room"))
    					EventQueue.invokeLater(new ResultAssumptionRunnable(true,getZombie(nextPlayerName),new Room(nextCard.getName()),false));

		    	
					log.printLog("Player: Scrivo nell'area delle info chi mi ha confutato l'ipotesi ("+nextPlayerName+") e con che carta");
					
    				//comunicami chi mi ha confutato l'ipotesi e con che carta
    				showNewInfo(nextPlayerName, ""+nextCard.getName(),"Refuted");
    				
    				log.printLog("Player: scarta la carta che mi ha confutato l'ipotesi da quelle della soluzione");
    				
    				//elimina la carta dalle carte possibili per la soluzione
    				EventQueue.invokeLater(new DiscardCardRunnable(nextCard));
		    	
    				log.printLog("Player: chiamo multicastSendInfo per comunicare a tutti l'info che ho terminato il turno e poi " +
    						"multicastEndTurn per eseguire effettivamente la terminazione del mio turno");
    				
		    	
    				System.out.println("Player: Tutti mi hanno risposto, la mia ipotesi e' stata confutata da " +nextPlayer.getNickname());
		    		    	
    			}
				
    		
    			else if(allAnswer && !refuteFound){ 	//nessuno ha confutato la mia ipotesi
				
    				
    				if(cheat){ //ho barato, termino il mio turno
    					
    					
    					log.printLog("Player: Tutti mi hanno risposto, nessuno ha confutato la mia ipotesi ma ho barato");
    					
    					log.printLog("Player: viene mostrata la finestra che mi comunica che nessuno ha confutato la mia ipotesi ma ho barato." +
    							"Viene mostrata l'info anche nella area delle info");
    					//fai apparire la finestra che mi dice che nessuno mi ha confutato l'ipotesi (ma ho barato)
    					EventQueue.invokeLater(new ResultAssumptionRunnable(false,null,null,true));
    					multicastSendInfo(nickname,"","Cheat");
    					
    					cheat=false;
    				}
    			
    				else{ //nessuno mi ha confutato l'ipotesi e non ho barato
    				
    					log.printLog("Player: Tutti mi hanno risposto, nessuno ha confutato la mia ipotesi, non ho barato.");
    					EventQueue.invokeLater(new ResultAssumptionRunnable(false,null,null,false));
    					multicastSendInfo(nickname,"","NotCheat");
    				}
    				
    			}
    			
    			refuteAnswers=null;
				refutePlayerTurn=null;
				
				EventQueue.invokeLater(new Runnable(){
					public void run(){
						wind.getCenterPanel().setPlayerTurn(true);
						wind.getEastPanel().setSolBtn();
					}
				});
    					
			}
    		
		
    }
    
    
    /**
     * Invia un'ipotesi agli altri player
     */
    
    public synchronized void sendAssumption(Character character, Weapon weapon, Room room){
    	   	
    	log.printLog("Player: ho fatto un'ipotesi. Invio il multicastCheckAssumption per far controllare " +
    			"agli altri player la mia ipotesi e poi vado nello stato WAITING_REFUTE");
    	
   
		wind.setIsAssumptionChecked(false);
	
		myState.sendAssumption();

    	
		refuteAnswers = new LinkedHashMap<String,Card>();
		refutePlayerTurn = new LinkedHashMap<Integer,String>();
		log.printLog("Player: (sendAssumption). Ho inizializzato le mappe per verificare le risposte");
		
		
		synchronized (playGroupMembers){
    		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
    		
    			Zombie z = entry.getValue();
    			if(!z.getNickname().equals(nickname)){	/* se e' il mio zombie non deve andare nello 
    				stato REFUTE_ASSUMPTION per controllare se puo' confutare l'ipotesi*/
    				log.printLog("Zombie: Il mio zombie " +z.getNickname()+" ha ricevuto la mia ipotesi e va nello stato REFUTE_ASSUMPTION");
    				z.setAssumption(nickname, character, weapon, room);
    				z.state.getAssumption();		
    			}
       		}
    	}
		
		String playerAccusedName=null;
		Zombie playerAccused=null;
		
		//recupero il nickname del player associato al personaggio che ho accusato
		for (Map.Entry<String, Zombie> entry : playGroupMembers.entrySet()) {
			
			if(entry.getValue().getPawn().getName().equals(character.getName())){
				playerAccusedName=entry.getKey();
				playerAccused=entry.getValue();
				break;
			}
			
		}
		
		String roomName= room.getName();
		
		
		if(playerAccusedName!=null && (!playerAccusedName.equals(nickname)) && 
				(!(playerAccused.getPawn().isInRoom() && playerAccused.getPawn().getRoomName().equals(roomName)))){
			/* non eseguo questo blocco se la pedina associata al sospettato non fa parte della partita
			 * oppure se mi sono autosospettato e quindi la mia pedina e' gia' nella stanza
			 * oppure se chi ho accusato era gia' nella stanza in cui sto facendo l'ipotesi
			*/
			int[] coords= wind.getBoard().firstFreeCell(roomName);
		
			int xFreeRoom=coords[0];
			int yFreeRoom=coords[1];
		
		
			//sposto la pedina di chi ho accusato nella stanza
			wind.getBoard().movePawn(getZombie(playerAccusedName), xFreeRoom, yFreeRoom);
		
			// tutti devono spostare la pedina di chi e' stato accusato
			multicastMovePawn(playerAccusedName,xFreeRoom,yFreeRoom);
		
		}
		
		multicastCheckAssumption(nickname,character,weapon,room);
		
    }
    	
    
	// LISTENER PER AGGIORNARE LA GRAFICA
	
    public class ContinueButtonListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {

			
			if (e.getActionCommand().equals("continue")) {
				pawnDialog.closePawnSelectionDialog();
				pawnDialog= null;
				// rendo solo invisibile la finestra di registrazione
		    	regWind.hideGroupWindow();
		    	/* 
		    	 * Necessario non eliminare dalla memoria la finestra gruppi altrimenti viene 
				 * a mancare il riferimento per tornare dalla MainWindow alla finestra gruppi 
				 */
		    	wind.showMainWindow();
			}
		}
    	
    }
    
    
    
  // THREADS
    
    
 /** classe che implementa runnable per selezionare una carta come non appartenente alla soluzione del gioco */
    private class DiscardCardRunnable implements Runnable{
    	
    	private Card card;
    	
    	public DiscardCardRunnable(Card card){
    		
    		this.card=card;
    		
    	}
    	
    	@Override
    	public void run() {

    		wind.discardCard(card);
    	} 	
    	
    }  
    
 /** classe che implementa runnable per mostrare nell'interfaccia le carte che mi sono state distribuite */
    private class ShowCardsRunnable implements Runnable{
    	
    	private LinkedList<Card> card;
    	
    	
    	public ShowCardsRunnable(LinkedList<Card> card){
    		
    	this.card=card;
    		
    	}
    	
    	@Override
    	public void run() {
    		
    		wind.showMyCard(card);
    	} 	
    	
    }  
    
    
 /** classe che implementa runnable per aggiungere nickname nell'interfaccia grafica */
    private class AddNicknameRunnable implements Runnable{
    	
    	Zombie player;
    	
    	public AddNicknameRunnable(Zombie player){
    		
    		this.player= player;
    		
    	}
    	
    	@Override
    	public void run() {
    		
    		wind.getNicknameArea().addNicknameLine(player);
    	} 	
    	
    }

    
    
 /** classe che implementa runnable per visualizzare il risultato dell'ipotesi formulata dal player */
    
    private class ResultAssumptionRunnable implements Runnable{
    	
    	Zombie player;
    	boolean refuted;
    	Card c;
    	boolean cheat;
    	
    	public ResultAssumptionRunnable(boolean refuted,Zombie player, Card c, boolean cheat){
    		this.refuted=refuted;
    		this.c=c;
    		this.cheat=cheat;
    		this.player= player;
    		
    	}
    	
    	@Override
    	public void run() {
    		
    		wind.createResWind(refuted,player,c,cheat);
    	} 	
    	
    }
    
    /** classe che implementa runnable per impostare player come zombie nella nicknames area */
    
    private class ZombieNicknameRunnable implements Runnable{
    	
    	Zombie player;
    	
    	public ZombieNicknameRunnable(Zombie player){
    		
    		this.player= player;
    		
    	}
    	
    	@Override
    	public void run() {
    		
    		wind.getNicknameArea().zombieNicknameLine(player);
    	} 	
    	
    }
 
 /** classe che implementa runnable per mostrare la finestra vincitore/perdente nell'interfaccia grafica */

 private class EndWindRunnable implements Runnable{
 	
 	Zombie player;
 	boolean endGame;
 	String type;
 	
 	public EndWindRunnable(Zombie player,String type,boolean endGame){
 		
 		this.player= player;
 		this.endGame=endGame;
 		this.type=type;
 	}
 	
 	@Override
 	public void run() {
 		
		wind.createWinLoseWind(player, type, endGame);
 	} 	
 	
 }
 
 
 
 /** classe che implementa runnable per creare la finestra di confutazione ipotesi */

 private class RefutWindowRunnable implements Runnable{
 	
 	Zombie player;
 	Character character;
 	Weapon weapon;
 	Room room;
 	
 	public RefutWindowRunnable(Zombie player,Character character, Weapon weapon, Room room){
 		
 		this.player= player;
 		this.character=character;
 		this.weapon=weapon;
 		this.room=room;
 
 	}
 	
 	@Override
 	public void run() {
 		
 		wind.createRefuteWind(player,character,weapon,room);
 	} 	
 	
 }
 
 
 /** classe che implementa runnable per inviare messaggio di chat nell'interfaccia grafica */

 private class ChatMessageRunnable implements Runnable{
 	
 	String playerName;
 	String msg;
 	boolean isPlaying;
 	
 	public ChatMessageRunnable(String playerName,String msg, boolean isPlaying){
 		
 		this.playerName= playerName;
 		this.msg=msg;
 		this.isPlaying= isPlaying;
 		
 	}
 	
 	@Override
 	public void run() {
 		if (isPlaying) {
 			wind.updateChat(playerName,msg);
 		} else {
 			regWind.updateChat(playerName,msg);
 		}
 	} 	
 	
 }
 
 
 /** classe che implementa runnable per gestire ricezione di informazioni nell'interfaccia grafica */

 private class InfoRunnable implements Runnable{
 	
 	Zombie player;
 	String info,type;
 	
 	public InfoRunnable(Zombie player,String info,String type){
 		
 		this.player= player;
 		this.info=info;
 		this.type=type;
 		
 	}
 	
 	@Override
 	public void run() {
 		
 		if(wind!=null) // cerco di prevenire la NullPointerException nel caso in cui il player sia tornato alla selezione gruppi
 			wind.writeInfo(player,info,type);
 		
 	} 	
 	
 }
 
 
 /** classe che implementa runnable per impostare le carte della soluzione nella MainWindow */

 private class SetSolutionRunnable implements Runnable{
 	
	 Character character;
	 Weapon weapon;
	 Room room;
 	
 	public SetSolutionRunnable(Character character, Weapon weapon, Room room){
 		
 		this.character=character;
 		this.weapon=weapon;
 		this.room=room;
 		
 	}
 	
 	@Override
 	public void run() {
 		
 		wind.setSol(room,weapon,character);
 		
 	} 	
 	
 }
 
 /** classe che implementa runnable per spostare una pedina sul tabellone*/

 private class MovePawnRunnable implements Runnable{
 	
 	Zombie player;
 	int x,y;
 	
 	
 	public MovePawnRunnable(Zombie player,int x, int y){
 		
 		this.player= player;
 		this.x=x;
 		this.y=y;
 		
 	}
 	
 	@Override
 	public void run() {
 		
 		wind.getBoard().movePawn(player,x,y);
 		
 	} 	
 	
 }
 
 
 /** classe che implementa runnable per aggiungere una pedina nella rispettiva posizione iniziale del tabellone */

 private class AddPawnRunnable implements Runnable{
 	
 	Zombie player;
 	
 	
 	public AddPawnRunnable(Zombie player){
 		
 		this.player= player;
 		
 	}
 	
 	@Override
 	public void run() {
 		
 		wind.getBoard().addPawn(player);
 		
 	} 	
 	
 }
 
 
 /** classe che implementa runnable per rimuovere una pedina dal tabellone */

 private class RemovePawnRunnable implements Runnable{
 	
 	Zombie player;
 	
 	
 	public RemovePawnRunnable(Zombie player){
 		
 		this.player= player;
 		
 	}
 	
 	@Override
 	public void run() {
 		
 		wind.getBoard().removePawn(player);
 		
 	} 	
 	
 }
 
 
 /** classe che implementa runnable per gestire cambio di turno nella nickname area*/

 private class ChangeTurnRunnable implements Runnable{
 	
 	Zombie player;
 	
 	public ChangeTurnRunnable(Zombie player){
 		
 		this.player= player;
 		
 	}
 	
 	@Override
 	public void run() {
 		
		wind.plChangeTurn(player);
 	} 	
 	
 } 
 
 /** classe che implementa runnable per gestire cambio di proprietario nella nicknames area */

 private class ChangeOwnerRunnable implements Runnable{
 	
 	Zombie player;
 	
 	public ChangeOwnerRunnable(Zombie player){
 		
 		this.player= player;
 		
 	}
 	
 	@Override
 	public void run() {
 		
		wind.grpChangeOwner(player);
 	} 	
 	
 } 

 
 	/**
 	 * Aggiornamento della lista dei nomi dei gruppi
 	 */
	private class UpdateGroupsList implements Runnable {
		
		private String operation;
		private String groupName;
		
		/**
		 * Ripulisce la lista dei nomi dei gruppi
		 */
		public UpdateGroupsList() {
			this.operation= "clear";
		}
		
		/**
		 * Aggiunge o rimuovere un nome di gruppo
		 * @param groupName nome da aggiungere/rimuovere
		 * @param op "add" oppure "del"
		 */
		public UpdateGroupsList(String groupName, String op) {
			if (op.equals("add") || op.equals("del")) {
				this.operation= op;
				this.groupName= groupName;
			} else {
				throw new RuntimeException("Operazione " + op + " non permessa");
			}
		}
		
		@Override
		public void run() {
			if (operation.equals("add")) {
				Player.this.regWind.addGroup(groupName);
			} else if (operation.equals("del")) {
				Player.this.regWind.delGroup(groupName);				
			} else {
				Player.this.regWind.clearGroupList();
			}
		}
	}
	
	/**
 	 * Aggiornamento della lista dei nomi dei membri del gruppo
 	 */
	private class UpdateMembersList implements Runnable {
		private String operation;
		private String memberName;
		
		/**
		 * Ripulisce la lista dei nomi dei membri
		 */
		public UpdateMembersList() {
			this.operation= "clear";
		}
		
		/**
		 * Aggiunge o rimuovere un membro
		 * @param memberName nome da aggiungere/rimuovere
		 * @param op "add" oppure "del"
		 */
		public UpdateMembersList(String memberName, String op) {
			if (op.equals("add") || op.equals("del")) {
				this.operation= op;
				this.memberName= memberName;
			} else {
				throw new RuntimeException("Operazione " + op + " non permessa");
			}
		}
		
		@Override
		public void run() {
			if (operation.equals("add")) {
				Player.this.regWind.addMember(memberName);
			} else if (operation.equals("del")) {
				Player.this.regWind.delMember(memberName);				
			} else {
				Player.this.regWind.clearMemberList();
			}
		}
	}
    
	/**
	 * Abilita/disabilita i pannelli della finestra di registrazione: il pannello
	 * con la lista dei gruppi, quello con la lista dei membri e quello della chat.
	 */
	private class EnableCluedoStartPanels implements Runnable {

		private boolean groups;
		private boolean members;
		private boolean chat;
		
		public EnableCluedoStartPanels(boolean enableGroups, boolean enableMembers, boolean enableChat) {
			this.groups= enableGroups;
			this.members= enableMembers;
			this.chat= enableChat;
		}
		
		@Override
		public void run() {
			Player.this.regWind.setGroupsPaneEnabled(groups);
			Player.this.regWind.setMembersPaneEnabled(members);
			Player.this.regWind.setChatPaneEnabled(chat);
		}
		
	}
	
	/**
	 * Permette di gestire il Pawn Selection Dialog
	 */
	private class PawnDialog implements Runnable {

		private String str= null;
		private List<ColorPawn> list= null;
		
		public PawnDialog(String str) {
			this.str=str;
		}
		
		public PawnDialog(List<ColorPawn> list) {
			this.list=list;
		}
		
		@Override
		public void run() {
			if (str!=null) {
				pawnDialog.addInfo(str);
			} else if (list!=null) {
				pawnDialog.enablePawn(list);
			}
		}
		
		
	}
	
	// TOLLERANZA AI GUASTI
	
	@Override
	public void stateChange(PlayerState previousState) {
		// timer rilevazione crash altri player
		if (myState.isGroupMember()) {
			if (!checkCrashTimer.isRunning()) {
				log.printLog("Avvio il crash-timer");
				checkCrashTimer.start();
			} //else System.out.println("Il crash-timer era gia' in esecuzione");
		} else {
			log.printLog("Fermo il crash-timer");
			checkCrashTimer.stop();
		}
		
		// timer refresh presso il Registration Server
		if (previousState.isGroupOwner()!=myState.isGroupOwner()) {
			if (myState.isGroupOwner()) {
				// sono diventato proprietario
				log.printLog("Avvio refresh gruppo");
				refreshTimer.startRefreshGroup();
			} else {
				// ho perso la proprieta' del gruppo
				log.printLog("Fermo refresh gruppo");
				refreshTimer.stopRefreshGrupp();
			}
		}
		if (previousState.isRegistered() != myState.isRegistered()) {
			if (myState.isRegistered()) {
				// mi sono registrato
				log.printLog("Avvio refresh player");
				refreshTimer.startRefreshPlayer();
			} else {
				// ho cancellato la mia registrazione
				log.printLog("Fermo refresh player");
				refreshTimer.stopRefreshPlayer();
			}
		}
		
		// TODO simulazione uscita contemporanea
		/*if (myState.isPlayStarted() && (
				nickname.equals("b") ||
				nickname.equals("d") ||
				nickname.equals("f") )
				) {
			exit();
		}*/
	}
		
	
	/** Il Listener per gestire il timer che controlla se gli altri player hanno fatto crash mentre io non ho il turno */
	private class TimerCheckCrash implements ActionListener {
		
		public void actionPerformed(ActionEvent e) {
			
			addTimerThread(new Runnable(){
				public void run(){
					if (!enable) return;
					
					boolean res=false;
						
					long time1, time2, time3, time4;
						 
					time1 = System.nanoTime();
					synchronized (Player.this) {
						time2 = System.nanoTime();
								myTrans.multicastAreYouAlive();					    
						time3 = System.nanoTime();
					}
					time4 = System.nanoTime();
					
					float totalTime= (float)(time4 - time1) / 1000;
					totalTime= (float)Math.round(totalTime) / 1000;
					float lockTime= (float)(time3 - time2) / 1000;
					lockTime= (float)Math.round(lockTime) / 1000;
					float waitingTime= (float)(time2 - time1) / 1000;
					waitingTime= (float)Math.round(waitingTime) / 1000;
					
			
					/*
					System.out.println("TimerCheckCrash: eseguito in " + totalTime + "ms, di cui: " +
						+ waitingTime + "ms attesa CS, " +
						+ lockTime + "ms dentro CS");
						*/
				}
			});
		
		}
		
	}
	

	/**
	 * Gestisce il timer relativo al rinfresco della registrazione del player
	 * e dell'eventuale gruppo di cui e' proprietario
	 */
	private class RefreshTimer {
		/**
		 * Periodo del timer
		 */
		private final long period= 3; // in secondi
		
		/**
		 * Il mio timer
		 */
		ScheduledThreadPoolExecutor timer;
		
		/**
		 * Usato per cancellare il timer del player
		 */
		private ScheduledFuture<?> playerSF;
		/**
		 * Runnable per rinfrescare il player
		 */
		private final Runnable playerRunnable = new Runnable() {
			@Override
			public void run() {
				if (!enable) return;
				//System.out.println("Refresh timer player \"" + nickname + "\"");
				RequestRefreshName req= new RequestRefreshName(nickname);
				try {
					myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req, true);
				} catch (Exception e) {
					// TODO no tolleranza reg srv
					//e.printStackTrace();
				}
			}
		};
		/**
		 * Usato per cancellare il timer del gruppo
		 */
		private ScheduledFuture<?> groupSF;
		/**
		 * Runnable per rinfrescare il gruppo
		 */
		private final Runnable groupRunnable = new Runnable() {
			@Override
			public void run() {
				if (!enable) return;
				//System.out.println("Refresh timer gruppo \"" + playGroup + "\"");
				RequestRefreshPlayGroup req= new RequestRefreshPlayGroup(playGroup);
				try {
					myTrans.blockingRequest(CommonConstants.REGSRV, regSrv, req, true);
				} catch (Exception e) {
					// TODO no tolleranza reg srv
					//e.printStackTrace();
				}
			}
		};
		
		/**
		 * Crea un nuovo LeaseTimer
		 * @param type deve essere <code>GROUP</code> o <code>PLAYER</code>
		 */
		public RefreshTimer() {
			timer= new ScheduledThreadPoolExecutor(1);
			//timer.setRemoveOnCancelPolicy(true);
			playerSF= null;
			groupSF= null;
		}
		
		/**
		 * Avvia il refresh del player se non e' gia' stato fatto
		 */
		public void startRefreshPlayer() {
			if (playerSF==null) {
				playerSF= timer.scheduleAtFixedRate(playerRunnable, period, period, TimeUnit.SECONDS);
			}
		}
		
		/**
		 * Ferma il refresh del player se e' in esecuzione
		 */
		public void stopRefreshPlayer() {
			if (playerSF!=null) {
				boolean b= playerSF.cancel(false);
				playerSF= null;
				if (!b) {
					String str= "Timer player \"" + nickname + "\": ";
					System.err.println(str + " il timer non puo' essere fermato");
				}
			}
		}

		/**
		 * Avvia il refresh del gruppo se non e' gia' stato fatto
		 */
		public void startRefreshGroup() {
			if (groupSF==null) {
				groupSF= timer.scheduleAtFixedRate(groupRunnable, period, period, TimeUnit.SECONDS);
			}
		}
		
		/**
		 * Ferma il refresh del gruppo se e' in esecuzione
		 */
		public void stopRefreshGrupp() {
			if (groupSF!=null) {
				boolean b= groupSF.cancel(false);
				groupSF= null;
				if (!b) {
					String str= "Timer gruppo \"" + playGroup + "\": ";
					System.err.println(str + " il timer non puo' essere fermato");
				}
			}
		}
	}
	
	  /**
     * Runnable che si occupa di recuperare i messaggi ricevuti e di inoltrarli al metodo che li gestisce
     */
    private class GetAndAssignMessages implements Runnable {
	
		@Override
		public void run() {
			while (!Thread.currentThread().isInterrupted()) {
				Message msg=null;
				try {
					msg = myTrans.getNextMessage();
				} catch (InterruptedException e) {
				}
				
				// prima di assegnare il messaggio, verifico se sono stato messo in attesa
				synchronized (stopConsuming) {
					while (stopConsuming.isLocked()) {
						try {
							stopConsuming.wait();
						} catch (InterruptedException e) {
						}
					}
				}
				
				assignMessage(msg);
						
			}
		}		
		
	}
    
    
    
    private synchronized void assignMessage(Message msg){
		
		Payload msgPayload = msg.getPayload();
		
		//se sono nello stato ALONE non devo considerare altri messaggi
		if(!myState.isGroupMember() && !((msgPayload instanceof NotifyAddPlayGroup) ||
				(msgPayload instanceof NotifyChangePlayGroupOwner) ||
				(msgPayload instanceof NotifyDelPlayGroup)))
						return;
		
		if(msg.getPayload() instanceof NotifyAddPlayGroup){
				
			NotifyAddPlayGroup payload = ((NotifyAddPlayGroup)msgPayload);
			
			notifyAddPlayGroup(payload.groupName,payload.ownerName,payload.ownerTrans);
			
		}
		else if(msg.getPayload() instanceof NotifyChangePlayGroupOwner){
			
			NotifyChangePlayGroupOwner payload = ((NotifyChangePlayGroupOwner)msgPayload);
			
			notifyChangePlayGroupOwner(payload.groupName,payload.newOwnerName,payload.newOwnerTrans);
			
		}
		else if(msg.getPayload() instanceof NotifyChatMessage){
			
			NotifyChatMessage payload = ((NotifyChatMessage)msgPayload);
			
			sendChatMessage(msg.getSender(), payload.message);
			
		}
		else if(msg.getPayload() instanceof NotifyCheckAssumption){
			
			NotifyCheckAssumption payload = ((NotifyCheckAssumption)msgPayload);
			
			checkAssumption(msg.getSender(),payload.character,payload.weapon,payload.room);
			
		}
		else if(msg.getPayload() instanceof NotifyDelPlayGroup){
			
			NotifyDelPlayGroup payload = ((NotifyDelPlayGroup)msgPayload);
			
			notifyDelPlayGroup(payload.groupName);
			
		}
		else if(msg.getPayload() instanceof NotifyEndTurn){
								
			endTurn(msg.getSender());
			
		}
		else if(msg.getPayload() instanceof NotifyInfo){
			
			NotifyInfo payload = ((NotifyInfo)msgPayload);
			
			sendInfo(payload.playerName,payload.info,payload.type);
			
		}
		else if(msg.getPayload() instanceof NotifyMovePawn){
			
			NotifyMovePawn payload = ((NotifyMovePawn)msgPayload);
			
			movePawn(msg.getSender(),payload.playerName,payload.x,payload.y);
		}
		else if(msg.getPayload() instanceof NotifyPlayerPawn){
			
			NotifyPlayerPawn payload = ((NotifyPlayerPawn)msgPayload);	
			
			notifyPlayerPawn(msg.getSender(),payload.playerPawn);
		}
		else if(msg.getPayload() instanceof NotifyRefuteAssumption){
			
			NotifyRefuteAssumption payload = ((NotifyRefuteAssumption)msgPayload);
			
			refuteAssumption(msg.getSender(), payload.card);
			
		}
		else if(msg.getPayload() instanceof NotifySetDeck){
			
			NotifySetDeck payload = ((NotifySetDeck)msgPayload);
			
			setDeck(msg.getSender(),payload.room,payload.weapon,payload.character, payload.deck);
			
		}
		else if(msg.getPayload() instanceof NotifyThrowTieBreakDice){
			
			NotifyThrowTieBreakDice payload = ((NotifyThrowTieBreakDice)msgPayload);
			
			throwTieBreakDice(msg.getSender(),payload.num);
			
		}
		else if(msg.getPayload() instanceof NotifyWinner){
			
			NotifyWinner payload = ((NotifyWinner)msgPayload);
			
			winner(msg.getSender(), payload.winner);
			
		}
		
		else if(msg.getPayload() instanceof NotifyChangeGroup){ //qualcuno vuole entrare/uscire
			
			NotifyChangeGroup payload = ((NotifyChangeGroup)msgPayload);
			
			if(payload.joinLeave){ //qualcuno e' entrato nel gruppo
				
				notifyGroupMembers(payload.transName, payload.trans, payload.joinLeave);
				
			}
			else if(!payload.joinLeave){ //qualcuno e' uscito da gruppo
				if(myState.isPlayStarted())
					notifyPlayerExited(payload.transName);	
				else
					notifyGroupMembers(payload.transName, payload.trans, payload.joinLeave);

			}
			
		}
		
		else if(msg.getPayload() instanceof NotifyReceiverCrashed){ //qualcuno ha fatto crash
			
			NotifyReceiverCrashed payload = ((NotifyReceiverCrashed)msgPayload);
			
			notifyPlayerCrashed(payload.transName); //il player payload.transName ha fatto crash
			
		}
				
	}
    
    
    @Override
	public synchronized Message respondToRequest(Message msg) {
    	
    	
    	throw new RuntimeException("Questo player non gestisce le richieste\n");
    	
 
	}

	 private class Lock {
         private boolean pending;
         
         public Lock() {
                 pending= false;
         }
         
         public void lock() {
                 pending= true;
         }
         public void unlock() {
                 pending= false;
         }
         
         public boolean isLocked() {
                 return pending;
         }
	 }
}
